aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Jin <kjin@google.com>2013-11-06 16:41:39 -0800
committerKevin Jin <kjin@google.com>2013-11-07 10:10:50 -0800
commitc39b04e0e5d3962153cd860d1430857fe625da90 (patch)
tree380746ddc8053f5efcb93153f4334ad611ae6be4
parent9c92f46280cf3943701e75349833c68b584992e2 (diff)
downloaddroiddriver-c39b04e0e5d3962153cd860d1430857fe625da90.tar.gz
consolidate XPath code into ByXPath
dumpUiElementTree now includes invisible UiElements Change-Id: Ib7e1346e4e16dac0a05bb911aec4389483daaf8a
-rw-r--r--src/com/google/android/droiddriver/DroidDriver.java4
-rw-r--r--src/com/google/android/droiddriver/base/BaseUiElement.java27
-rw-r--r--src/com/google/android/droiddriver/finders/ByXPath.java46
-rw-r--r--src/com/google/android/droiddriver/instrumentation/InstrumentationContext.java2
-rw-r--r--src/com/google/android/droiddriver/instrumentation/InstrumentationDriver.java4
-rw-r--r--src/com/google/android/droiddriver/scroll/Scrollers.java15
-rw-r--r--src/com/google/android/droiddriver/scroll/StepBasedScroller.java3
-rw-r--r--src/com/google/android/droiddriver/uiautomation/UiAutomationContext.java2
-rw-r--r--src/com/google/android/droiddriver/uiautomation/UiAutomationElement.java1
9 files changed, 61 insertions, 43 deletions
diff --git a/src/com/google/android/droiddriver/DroidDriver.java b/src/com/google/android/droiddriver/DroidDriver.java
index 5421718..cd1caca 100644
--- a/src/com/google/android/droiddriver/DroidDriver.java
+++ b/src/com/google/android/droiddriver/DroidDriver.java
@@ -124,6 +124,10 @@ public interface DroidDriver {
* Dumps the UiElement tree to a file to help debug. The tree is based on the
* last used root UiElement if it exists, otherwise
* {@link #refreshUiElementTree} is called.
+ * <p>
+ * The dump may contain invisible UiElements that are not used in the finding
+ * algorithm.
+ * </p>
*
* @param path the path of file to save the tree
* @return whether the dumping succeeded
diff --git a/src/com/google/android/droiddriver/base/BaseUiElement.java b/src/com/google/android/droiddriver/base/BaseUiElement.java
index 9cddc71..8bac986 100644
--- a/src/com/google/android/droiddriver/base/BaseUiElement.java
+++ b/src/com/google/android/droiddriver/base/BaseUiElement.java
@@ -21,12 +21,10 @@ import android.graphics.Rect;
import com.google.android.droiddriver.UiElement;
import com.google.android.droiddriver.actions.Action;
import com.google.android.droiddriver.actions.ClickAction;
-import com.google.android.droiddriver.actions.InputInjector;
import com.google.android.droiddriver.actions.SwipeAction;
import com.google.android.droiddriver.actions.TextAction;
import com.google.android.droiddriver.exceptions.ElementNotVisibleException;
import com.google.android.droiddriver.finders.Attribute;
-import com.google.android.droiddriver.finders.ByXPath;
import com.google.android.droiddriver.scroll.Direction.PhysicalDirection;
import com.google.android.droiddriver.util.Logs;
import com.google.common.base.Objects;
@@ -36,9 +34,6 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
-import org.w3c.dom.Element;
-
-import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
@@ -48,8 +43,6 @@ import java.util.concurrent.FutureTask;
* Base UiElement that implements the common operations.
*/
public abstract class BaseUiElement implements UiElement {
- private WeakReference<Element> domNode;
-
@SuppressWarnings("unchecked")
@Override
public <T> T get(Attribute attribute) {
@@ -236,6 +229,11 @@ public abstract class BaseUiElement implements UiElement {
for (Attribute attr : Attribute.values()) {
addAttribute(toStringHelper, attr, get(attr));
}
+ if (!isVisible()) {
+ toStringHelper.addValue("NOT visible");
+ } else {
+ toStringHelper.add("visibleBounds", getVisibleBounds());
+ }
return toStringHelper.toString();
}
@@ -250,19 +248,4 @@ public abstract class BaseUiElement implements UiElement {
}
}
}
-
- /**
- * Used internally in {@link ByXPath}. Returns the DOM node representing this
- * UiElement. The DOM is constructed from the UiElement tree.
- * <p>
- * TODO: move this to {@link ByXPath}. This requires a BiMap using
- * WeakReference for both keys and values, which is error-prone. This will be
- * deferred until we decide whether to clear cache upon getRootElement.
- */
- public Element getDomNode() {
- if (domNode == null || domNode.get() == null) {
- domNode = new WeakReference<Element>(ByXPath.buildDomNode(this));
- }
- return domNode.get();
- }
}
diff --git a/src/com/google/android/droiddriver/finders/ByXPath.java b/src/com/google/android/droiddriver/finders/ByXPath.java
index dbe4363..a96bd51 100644
--- a/src/com/google/android/droiddriver/finders/ByXPath.java
+++ b/src/com/google/android/droiddriver/finders/ByXPath.java
@@ -25,6 +25,9 @@ import com.google.android.droiddriver.util.FileUtils;
import com.google.android.droiddriver.util.Logs;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
@@ -50,10 +53,17 @@ import javax.xml.xpath.XPathFactory;
*/
public class ByXPath implements Finder {
private static final XPath XPATH_COMPILER = XPathFactory.newInstance().newXPath();
- private static final String UI_ELEMENT = "UiElement";
// document needs to be static so that when buildDomNode is called recursively
// on children they are in the same document to be appended.
+ // TODO: move the below two to DroidDriverContext?
private static Document document;
+ private static BiMap<BaseUiElement, Element> domBiMap = HashBiMap.create();
+
+ public static void clearData() {
+ domBiMap.clear();
+ document = null;
+ }
+
private final String xPathString;
private final XPathExpression xPathExpression;
@@ -73,7 +83,7 @@ public class ByXPath implements Finder {
@Override
public UiElement find(UiElement context) {
- Element domNode = ((BaseUiElement) context).getDomNode();
+ Element domNode = getDomNode((BaseUiElement) context, UiElement.VISIBLE);
try {
getDocument().appendChild(domNode);
Element foundNode = (Element) xPathExpression.evaluate(domNode, XPathConstants.NODE);
@@ -82,7 +92,7 @@ public class ByXPath implements Finder {
throw new ElementNotFoundException(this);
}
- UiElement match = (UiElement) foundNode.getUserData(UI_ELEMENT);
+ UiElement match = domBiMap.inverse().get(foundNode);
Logs.log(Log.INFO, "Found match: " + match);
return match;
} catch (XPathExpressionException e) {
@@ -109,15 +119,24 @@ public class ByXPath implements Finder {
}
/**
- * Used internally in {@link BaseUiElement}.
+ * Returns the DOM node representing this UiElement.
*/
- public static Element buildDomNode(BaseUiElement uiElement) {
+ private static Element getDomNode(BaseUiElement uiElement, Predicate<? super UiElement> predicate) {
+ Element domNode = domBiMap.get(uiElement);
+ if (domNode == null) {
+ domNode = buildDomNode(uiElement, predicate);
+ }
+ return domNode;
+ }
+
+ private static Element buildDomNode(BaseUiElement uiElement,
+ Predicate<? super UiElement> predicate) {
String className = uiElement.getClassName();
if (className == null) {
className = "UNKNOWN";
}
Element element = getDocument().createElement(XPaths.tag(className));
- element.setUserData(UI_ELEMENT, uiElement, null /* UserDataHandler */);
+ domBiMap.put(uiElement, element);
setAttribute(element, Attribute.CLASS, className);
setAttribute(element, Attribute.RESOURCE_ID, uiElement.getResourceId());
@@ -136,12 +155,8 @@ public class ByXPath implements Finder {
setAttribute(element, Attribute.SELECTED, uiElement.isSelected());
element.setAttribute(Attribute.BOUNDS.getName(), uiElement.getBounds().toShortString());
- // TODO: Make VISIBLE optional so that the DOM dump contains all children.
- // This is especially useful for InstrumentationDriver which sees more than
- // uiautomatorviewer does. For now users can temporarily change VISIBLE to
- // null, rebuild test apk and run to get a more comprehensive dump.
- for (BaseUiElement child : uiElement.getChildren(UiElement.VISIBLE)) {
- element.appendChild(child.getDomNode());
+ for (BaseUiElement child : uiElement.getChildren(predicate)) {
+ element.appendChild(getDomNode(child, predicate));
}
return element;
}
@@ -160,17 +175,22 @@ public class ByXPath implements Finder {
}
public static boolean dumpDom(String path, BaseUiElement uiElement) {
+ // find() filters invisible UiElements, but this is for debugging and
+ // invisible UiElements may be of interest.
+ clearData();
BufferedOutputStream bos = null;
try {
bos = FileUtils.open(path);
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
- transformer.transform(new DOMSource(uiElement.getDomNode()), new StreamResult(bos));
+ transformer.transform(new DOMSource(getDomNode(uiElement, null)), new StreamResult(bos));
Logs.log(Log.INFO, "Wrote dom to " + path);
} catch (Exception e) {
Logs.log(Log.ERROR, e, "Failed to transform node");
return false;
} finally {
+ // We built DOM with invisible UiElements. Don't use it for find()!
+ clearData();
if (bos != null) {
try {
bos.close();
diff --git a/src/com/google/android/droiddriver/instrumentation/InstrumentationContext.java b/src/com/google/android/droiddriver/instrumentation/InstrumentationContext.java
index 27fa975..f535ccb 100644
--- a/src/com/google/android/droiddriver/instrumentation/InstrumentationContext.java
+++ b/src/com/google/android/droiddriver/instrumentation/InstrumentationContext.java
@@ -25,6 +25,7 @@ import android.view.View;
import com.google.android.droiddriver.actions.InputInjector;
import com.google.android.droiddriver.base.DroidDriverContext;
import com.google.android.droiddriver.exceptions.ActionException;
+import com.google.android.droiddriver.finders.ByXPath;
import com.google.common.collect.MapMaker;
import java.util.Map;
@@ -80,5 +81,6 @@ class InstrumentationContext implements DroidDriverContext {
@Override
public void clearData() {
map.clear();
+ ByXPath.clearData();
}
}
diff --git a/src/com/google/android/droiddriver/instrumentation/InstrumentationDriver.java b/src/com/google/android/droiddriver/instrumentation/InstrumentationDriver.java
index 11cf461..c8630ec 100644
--- a/src/com/google/android/droiddriver/instrumentation/InstrumentationDriver.java
+++ b/src/com/google/android/droiddriver/instrumentation/InstrumentationDriver.java
@@ -19,12 +19,10 @@ package com.google.android.droiddriver.instrumentation;
import android.app.Activity;
import android.app.Instrumentation;
import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.graphics.Bitmap.Config;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
diff --git a/src/com/google/android/droiddriver/scroll/Scrollers.java b/src/com/google/android/droiddriver/scroll/Scrollers.java
index ddf9610..6272955 100644
--- a/src/com/google/android/droiddriver/scroll/Scrollers.java
+++ b/src/com/google/android/droiddriver/scroll/Scrollers.java
@@ -18,6 +18,7 @@ package com.google.android.droiddriver.scroll;
import android.app.UiAutomation;
+import com.google.android.droiddriver.scroll.Direction.Axis;
import com.google.android.droiddriver.scroll.Direction.DirectionConverter;
/**
@@ -35,14 +36,22 @@ public class Scrollers {
* the case of dynamic list, which shows more items when scrolling beyond the
* end. On the other hand, it's complex and needs more configuration.</li>
* </ul>
+ * Note if a {@link StepBasedScroller} is returned, it is constructed with
+ * arguments that apply to typical cases. You may want to customize them for
+ * specific cases. For instance, {@code perScrollTimeoutMillis} can be 0L if
+ * there are no asynchronously updated views. To that extent, this method
+ * serves as an example of how to construct rather {@link Scroller}s than
+ * providing the "official" {@link Scroller}.
*/
public static Scroller newScroller(UiAutomation uiAutomation) {
if (uiAutomation != null) {
- return new StepBasedScroller(new AccessibilityEventScrollStepStrategy(uiAutomation, 1000L,
- DirectionConverter.STANDARD_CONVERTER));
+ return new StepBasedScroller(100/* maxScrolls */, 1000L/* perScrollTimeoutMillis */,
+ Axis.VERTICAL, new AccessibilityEventScrollStepStrategy(uiAutomation, 1000L,
+ DirectionConverter.STANDARD_CONVERTER), true/* startFromBeginning */);
}
// TODO: A {@link Scroller} that directly jumps to the view if an
// InstrumentationDriver is used.
- return new StepBasedScroller(StaticSentinelStrategy.DEFAULT);
+ return new StepBasedScroller(100/* maxScrolls */, 1000L/* perScrollTimeoutMillis */,
+ Axis.VERTICAL, StaticSentinelStrategy.DEFAULT, true/* startFromBeginning */);
}
}
diff --git a/src/com/google/android/droiddriver/scroll/StepBasedScroller.java b/src/com/google/android/droiddriver/scroll/StepBasedScroller.java
index 07d7fb6..2ba8ceb 100644
--- a/src/com/google/android/droiddriver/scroll/StepBasedScroller.java
+++ b/src/com/google/android/droiddriver/scroll/StepBasedScroller.java
@@ -56,7 +56,8 @@ public class StepBasedScroller implements Scroller {
* @param maxScrolls the maximum number of scrolls. It should be large enough
* to allow any reasonable list size
* @param perScrollTimeoutMillis the timeout in millis that we poll for the
- * item after each scroll
+ * item after each scroll. 1000L is usually safe; if there are no
+ * asynchronously updated views, 0L is also a reasonable value.
* @param axis the axis this scroller can scroll
* @param startFromBeginning if {@code true},
* {@link #scrollTo(DroidDriver, Finder, Finder)} starts from the
diff --git a/src/com/google/android/droiddriver/uiautomation/UiAutomationContext.java b/src/com/google/android/droiddriver/uiautomation/UiAutomationContext.java
index 0b56d33..b85803b 100644
--- a/src/com/google/android/droiddriver/uiautomation/UiAutomationContext.java
+++ b/src/com/google/android/droiddriver/uiautomation/UiAutomationContext.java
@@ -23,6 +23,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import com.google.android.droiddriver.actions.InputInjector;
import com.google.android.droiddriver.base.DroidDriverContext;
+import com.google.android.droiddriver.finders.ByXPath;
import com.google.common.collect.MapMaker;
import java.util.Map;
@@ -74,6 +75,7 @@ class UiAutomationContext implements DroidDriverContext {
@Override
public void clearData() {
map.clear();
+ ByXPath.clearData();
}
public UiAutomation getUiAutomation() {
diff --git a/src/com/google/android/droiddriver/uiautomation/UiAutomationElement.java b/src/com/google/android/droiddriver/uiautomation/UiAutomationElement.java
index 7fc5056..c5f975a 100644
--- a/src/com/google/android/droiddriver/uiautomation/UiAutomationElement.java
+++ b/src/com/google/android/droiddriver/uiautomation/UiAutomationElement.java
@@ -128,7 +128,6 @@ public class UiAutomationElement extends BaseUiElement {
private Rect getVisibleBounds(AccessibilityNodeInfo node) {
if (!visible) {
- Logs.log(Log.DEBUG, "Node is invisible: " + node);
return new Rect();
}
Rect visibleBounds = getBounds();