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