BaseUiElement.java revision 29d66eeee5d30f7db747cceeb84defec961b4125
1436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov/* 2436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * Copyright (C) 2013 DroidDriver committers 3436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * 4436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * Licensed under the Apache License, Version 2.0 (the "License"); 5436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * you may not use this file except in compliance with the License. 6436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * You may obtain a copy of the License at 7436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * 8436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * http://www.apache.org/licenses/LICENSE-2.0 9436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * 10436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * Unless required by applicable law or agreed to in writing, software 11436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * distributed under the License is distributed on an "AS IS" BASIS, 12436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * See the License for the specific language governing permissions and 14436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * limitations under the License. 15436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov */ 16436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 17436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovpackage com.google.android.droiddriver.base; 18436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 19436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport com.google.android.droiddriver.UiElement; 20436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport com.google.android.droiddriver.actions.Action; 21436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport com.google.android.droiddriver.actions.ClickAction; 22436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport com.google.android.droiddriver.actions.InputInjector; 23436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport com.google.android.droiddriver.actions.SwipeAction; 24436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport com.google.android.droiddriver.actions.TextAction; 25436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport com.google.android.droiddriver.exceptions.ElementNotVisibleException; 26436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport com.google.android.droiddriver.finders.Attribute; 27436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport com.google.android.droiddriver.finders.ByXPath; 28436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport com.google.android.droiddriver.scroll.Direction.PhysicalDirection; 29436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport com.google.android.droiddriver.util.Logs; 30436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport com.google.common.base.Objects; 31436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport com.google.common.base.Objects.ToStringHelper; 32436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport com.google.common.base.Predicate; 33436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport com.google.common.base.Predicates; 34436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport com.google.common.collect.Lists; 35436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 36436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport org.w3c.dom.Element; 37436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 38436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport java.lang.ref.WeakReference; 39436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport java.util.List; 40436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport java.util.concurrent.Callable; 41436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovimport java.util.concurrent.FutureTask; 42436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 43436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov/** 44436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * Abstract implementation with common methods already implemented. 45436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov */ 46436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanovpublic abstract class BaseUiElement implements UiElement { 47436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov private WeakReference<Element> domNode; 48436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 49436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov @Override 50436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov public <T> T get(Attribute attribute) { 51436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov return attribute.getValue(this); 52436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 53436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 54436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov @Override 55436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov public boolean perform(Action action) { 56436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov Logs.call(this, "perform", action); 57436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov checkVisible(); 58436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov return performAndWait(action); 59436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 60436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 61436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov protected boolean doPerform(Action action) { 62436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov return action.perform(getInjector(), this); 63436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 64436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 65436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov protected void doPerformAndWait(FutureTask<Boolean> futureTask, long timeoutMillis) { 66436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov // ignores timeoutMillis; subclasses can override this behavior 67436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov futureTask.run(); 68436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 69436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 70436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov private boolean performAndWait(final Action action) { 71436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov // timeoutMillis <= 0 means no need to wait 72436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov if (action.getTimeoutMillis() <= 0) { 73436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov return doPerform(action); 74436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 75436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 76436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov FutureTask<Boolean> futureTask = new FutureTask<Boolean>(new Callable<Boolean>() { 77436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov @Override 78436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov public Boolean call() { 79436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov return doPerform(action); 80436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 81436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov }); 82436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov doPerformAndWait(futureTask, action.getTimeoutMillis()); 83436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov try { 84436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov return futureTask.get(); 85436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } catch (Exception e) { 86436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov // should not reach here b/c futureTask has run 87436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov return false; 88436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 89436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 90436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 91436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov @Override 92436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov public void setText(String text) { 93436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov perform(new TextAction(text)); 94436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov // TypeAction may not be effective immediately and reflected bygetText(), 95436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov // so the following will fail. 96436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov // if (Logs.DEBUG) { 97436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov // String actual = getText(); 98436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov // if (!text.equals(actual)) { 99436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov // throw new DroidDriverException(String.format( 100436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov // "setText failed: expected=\"%s\", actual=\"%s\"", text, actual)); 101436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov // } 102436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov // } 103436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 104436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 105436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov @Override 106436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov public void click() { 107436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov perform(ClickAction.SINGLE); 108436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 109436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 110436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov @Override 111436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov public void longClick() { 112436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov perform(ClickAction.LONG); 113436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 114436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 115436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov @Override 116436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov public void doubleClick() { 117436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov perform(ClickAction.DOUBLE); 118436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 119436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 120436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov @Override 121436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov public void scroll(PhysicalDirection direction) { 122436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov perform(SwipeAction.toScroll(direction)); 123436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 124436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 125436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov @Override 126436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov public abstract BaseUiElement getChild(int index); 127436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 128436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov protected abstract InputInjector getInjector(); 129436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 130436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov private void checkVisible() { 131436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov if (!isVisible()) { 132436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov throw new ElementNotVisibleException(this); 133436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 134436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 135436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 136436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov @Override 137436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov public List<UiElement> getChildren(Predicate<? super UiElement> predicate) { 138436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov if (predicate == null) { 139436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov predicate = Predicates.notNull(); 140436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } else { 141436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov predicate = Predicates.and(Predicates.notNull(), predicate); 142436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 143436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 144436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov List<UiElement> list = Lists.newArrayList(); 145436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov for (int i = 0; i < getChildCount(); i++) { 146436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov UiElement child = getChild(i); 147436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov if (predicate.apply(child)) { 148436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov list.add(child); 149436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 150436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 151436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov return list; 152436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 153436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 154436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov @Override 155436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov public String toString() { 156436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov ToStringHelper toStringHelper = Objects.toStringHelper(this); 157436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov for (Attribute attr : Attribute.values()) { 158436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov addAttribute(toStringHelper, attr, get(attr)); 159436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 160436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov return toStringHelper.toString(); 161436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 162436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 163436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov private static void addAttribute(ToStringHelper toStringHelper, Attribute attr, Object value) { 164436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov if (value != null) { 165436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov if (value instanceof Boolean) { 166436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov if ((Boolean) value) { 167436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov toStringHelper.addValue(attr.getName()); 168436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 169436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } else { 170436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov toStringHelper.add(attr.getName(), value); 171436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 172436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 173436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 174436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov 175436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov /** 176436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * Used internally in {@link ByXPath}. Returns the DOM node representing this 177436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * UiElement. The DOM is constructed from the UiElement tree. 178436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * <p> 179436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * TODO: move this to {@link ByXPath}. This requires a BiMap using 180436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * WeakReference for both keys and values, which is error-prone. This will be 181436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov * deferred until we decide whether to clear cache upon getRootElement. 182436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov */ 183436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov public Element getDomNode() { 184436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov if (domNode == null || domNode.get() == null) { 185436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov domNode = new WeakReference<Element>(ByXPath.buildDomNode(this)); 186436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 187436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov return domNode.get(); 188436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov } 189436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov} 190436e89c602e787e7a27dd6624b09beed41a0da8aDmitriy Ivanov