diff options
author | Kevin Jin <kjin@google.com> | 2013-11-06 16:41:39 -0800 |
---|---|---|
committer | Kevin Jin <kjin@google.com> | 2013-11-07 10:10:50 -0800 |
commit | c39b04e0e5d3962153cd860d1430857fe625da90 (patch) | |
tree | 380746ddc8053f5efcb93153f4334ad611ae6be4 | |
parent | 9c92f46280cf3943701e75349833c68b584992e2 (diff) | |
download | droiddriver-c39b04e0e5d3962153cd860d1430857fe625da90.tar.gz |
consolidate XPath code into ByXPath
dumpUiElementTree now includes invisible UiElements
Change-Id: Ib7e1346e4e16dac0a05bb911aec4389483daaf8a
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(); |