• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 DroidDriver committers
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.android.droiddriver.finders;
18 
19 import static com.google.android.droiddriver.util.Preconditions.checkNotNull;
20 
21 import com.google.android.droiddriver.UiElement;
22 import com.google.android.droiddriver.exceptions.ElementNotFoundException;
23 
24 /**
25  * Convenience methods to create commonly used finders.
26  */
27 public class By {
28   private static final MatchFinder ANY = new MatchFinder(null);
29 
30   /** Matches any UiElement. */
any()31   public static MatchFinder any() {
32     return ANY;
33   }
34 
35   /** Matches a UiElement whose {@code attribute} is {@code true}. */
is(Attribute attribute)36   public static MatchFinder is(Attribute attribute) {
37     return new MatchFinder(Predicates.attributeTrue(attribute));
38   }
39 
40   /**
41    * Matches a UiElement whose {@code attribute} is {@code false} or is not set.
42    */
not(Attribute attribute)43   public static MatchFinder not(Attribute attribute) {
44     return new MatchFinder(Predicates.attributeFalse(attribute));
45   }
46 
47   /** Matches a UiElement by resource id. */
resourceId(String resourceId)48   public static MatchFinder resourceId(String resourceId) {
49     return new MatchFinder(Predicates.attributeEquals(Attribute.RESOURCE_ID, resourceId));
50   }
51 
52   /** Matches a UiElement by package name. */
packageName(String name)53   public static MatchFinder packageName(String name) {
54     return new MatchFinder(Predicates.attributeEquals(Attribute.PACKAGE, name));
55   }
56 
57   /** Matches a UiElement by the exact text. */
text(String text)58   public static MatchFinder text(String text) {
59     return new MatchFinder(Predicates.attributeEquals(Attribute.TEXT, text));
60   }
61 
62   /** Matches a UiElement whose text matches {@code regex}. */
textRegex(String regex)63   public static MatchFinder textRegex(String regex) {
64     return new MatchFinder(Predicates.attributeMatches(Attribute.TEXT, regex));
65   }
66 
67   /** Matches a UiElement whose text contains {@code substring}. */
textContains(String substring)68   public static MatchFinder textContains(String substring) {
69     return new MatchFinder(Predicates.attributeContains(Attribute.TEXT, substring));
70   }
71 
72   /** Matches a UiElement by content description. */
contentDescription(String contentDescription)73   public static MatchFinder contentDescription(String contentDescription) {
74     return new MatchFinder(Predicates.attributeEquals(Attribute.CONTENT_DESC, contentDescription));
75   }
76 
77   /** Matches a UiElement whose content description contains {@code substring}. */
contentDescriptionContains(String substring)78   public static MatchFinder contentDescriptionContains(String substring) {
79     return new MatchFinder(Predicates.attributeContains(Attribute.CONTENT_DESC, substring));
80   }
81 
82   /** Matches a UiElement by class name. */
className(String className)83   public static MatchFinder className(String className) {
84     return new MatchFinder(Predicates.attributeEquals(Attribute.CLASS, className));
85   }
86 
87   /** Matches a UiElement by class name. */
className(Class<?> clazz)88   public static MatchFinder className(Class<?> clazz) {
89     return className(clazz.getName());
90   }
91 
92   /** Matches a UiElement that is selected. */
selected()93   public static MatchFinder selected() {
94     return is(Attribute.SELECTED);
95   }
96 
97   /**
98    * Matches by XPath. When applied on an non-root element, it will not evaluate
99    * above the context element.
100    * <p>
101    * XPath is the domain-specific-language for navigating a node tree. It is
102    * ideal if the UiElement to match has a complex relationship with surrounding
103    * nodes. For simple cases, {@link #withParent} or {@link #withAncestor} are
104    * preferred, which can combine with other {@link MatchFinder}s in
105    * {@link #allOf}. For complex cases like below, XPath is superior:
106    *
107    * <pre>
108    * {@code
109    * <View><!-- a custom view to group a cluster of items -->
110    *   <LinearLayout>
111    *     <TextView text='Albums'/>
112    *     <TextView text='4 MORE'/>
113    *   </LinearLayout>
114    *   <RelativeLayout>
115    *     <TextView text='Forever'/>
116    *     <ImageView/>
117    *   </RelativeLayout>
118    * </View><!-- end of Albums cluster -->
119    * <!-- imagine there are other clusters for Artists and Songs -->
120    * }
121    * </pre>
122    *
123    * If we need to locate the RelativeLayout containing the album "Forever"
124    * instead of a song or an artist named "Forever", this XPath works:
125    *
126    * <pre>
127    * {@code //*[LinearLayout/*[@text='Albums']]/RelativeLayout[*[@text='Forever']]}
128    * </pre>
129    *
130    * @param xPath The xpath to use
131    * @return a finder which locates elements via XPath
132    */
xpath(String xPath)133   public static ByXPath xpath(String xPath) {
134     return new ByXPath(xPath);
135   }
136 
137   /**
138    * Returns a finder that uses the UiElement returned by first Finder as
139    * context for the second Finder.
140    * <p>
141    * typically first Finder finds the ancestor, then second Finder finds the
142    * target UiElement, which is a descendant.
143    * </p>
144    * Note that if the first Finder matches multiple UiElements, only the first
145    * match is tried, which usually is not what callers expect. In this case,
146    * allOf(second, withAncesor(first)) may work.
147    */
chain(Finder first, Finder second)148   public static ChainFinder chain(Finder first, Finder second) {
149     return new ChainFinder(first, second);
150   }
151 
getPredicates(MatchFinder... finders)152   private static Predicate<? super UiElement>[] getPredicates(MatchFinder... finders) {
153     @SuppressWarnings("unchecked")
154     Predicate<? super UiElement>[] predicates = new Predicate[finders.length];
155     for (int i = 0; i < finders.length; i++) {
156       predicates[i] = finders[i].predicate;
157     }
158     return predicates;
159   }
160 
161   /**
162    * Evaluates given {@finders} in short-circuit fashion in the order
163    * they are passed. Costly finders (for example those returned by with*
164    * methods that navigate the node tree) should be passed after cheap finders
165    * (for example the ByAttribute finders).
166    *
167    * @return a finder that is the logical conjunction of given finders
168    */
allOf(final MatchFinder... finders)169   public static MatchFinder allOf(final MatchFinder... finders) {
170     return new MatchFinder(Predicates.allOf(getPredicates(finders)));
171   }
172 
173   /**
174    * Evaluates given {@finders} in short-circuit fashion in the order
175    * they are passed. Costly finders (for example those returned by with*
176    * methods that navigate the node tree) should be passed after cheap finders
177    * (for example the ByAttribute finders).
178    *
179    * @return a finder that is the logical disjunction of given finders
180    */
anyOf(final MatchFinder... finders)181   public static MatchFinder anyOf(final MatchFinder... finders) {
182     return new MatchFinder(Predicates.anyOf(getPredicates(finders)));
183   }
184 
185   /**
186    * Matches a UiElement whose parent matches the given parentFinder. For
187    * complex cases, consider {@link #xpath}.
188    */
withParent(MatchFinder parentFinder)189   public static MatchFinder withParent(MatchFinder parentFinder) {
190     checkNotNull(parentFinder);
191     return new MatchFinder(Predicates.withParent(parentFinder.predicate));
192   }
193 
194   /**
195    * Matches a UiElement whose ancestor matches the given ancestorFinder. For
196    * complex cases, consider {@link #xpath}.
197    */
withAncestor(MatchFinder ancestorFinder)198   public static MatchFinder withAncestor(MatchFinder ancestorFinder) {
199     checkNotNull(ancestorFinder);
200     return new MatchFinder(Predicates.withAncestor(ancestorFinder.predicate));
201   }
202 
203   /**
204    * Matches a UiElement which has a visible sibling matching the given
205    * siblingFinder. This could be inefficient; consider {@link #xpath}.
206    */
withSibling(MatchFinder siblingFinder)207   public static MatchFinder withSibling(MatchFinder siblingFinder) {
208     checkNotNull(siblingFinder);
209     return new MatchFinder(Predicates.withSibling(siblingFinder.predicate));
210   }
211 
212   /**
213    * Matches a UiElement which has a visible child matching the given
214    * childFinder. This could be inefficient; consider {@link #xpath}.
215    */
withChild(MatchFinder childFinder)216   public static MatchFinder withChild(MatchFinder childFinder) {
217     checkNotNull(childFinder);
218     return new MatchFinder(Predicates.withChild(childFinder.predicate));
219   }
220 
221   /**
222    * Matches a UiElement whose descendant (including self) matches the given
223    * descendantFinder. This could be VERY inefficient; consider {@link #xpath}.
224    */
withDescendant(final MatchFinder descendantFinder)225   public static MatchFinder withDescendant(final MatchFinder descendantFinder) {
226     checkNotNull(descendantFinder);
227     return new MatchFinder(new Predicate<UiElement>() {
228       @Override
229       public boolean apply(UiElement element) {
230         try {
231           descendantFinder.find(element);
232           return true;
233         } catch (ElementNotFoundException enfe) {
234           return false;
235         }
236       }
237 
238       @Override
239       public String toString() {
240         return "withDescendant(" + descendantFinder + ")";
241       }
242     });
243   }
244 
245   /** Matches a UiElement that does not match the provided {@code finder}. */
246   public static MatchFinder not(MatchFinder finder) {
247     checkNotNull(finder);
248     return new MatchFinder(Predicates.not(finder.predicate));
249   }
250 
251   private By() {}
252 }
253