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 17package com.google.android.droiddriver.finders; 18 19import static com.google.android.droiddriver.util.Preconditions.checkNotNull; 20 21import com.google.android.droiddriver.UiElement; 22import com.google.android.droiddriver.exceptions.ElementNotFoundException; 23 24/** 25 * Convenience methods to create commonly used finders. 26 */ 27public class By { 28 private static final MatchFinder ANY = new MatchFinder(null); 29 30 /** Matches any UiElement. */ 31 public static MatchFinder any() { 32 return ANY; 33 } 34 35 /** Matches a UiElement whose {@code attribute} is {@code true}. */ 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 */ 43 public static MatchFinder not(Attribute attribute) { 44 return new MatchFinder(Predicates.attributeFalse(attribute)); 45 } 46 47 /** Matches a UiElement by resource id. */ 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. */ 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. */ 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}. */ 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}. */ 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. */ 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}. */ 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. */ 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. */ 88 public static MatchFinder className(Class<?> clazz) { 89 return className(clazz.getName()); 90 } 91 92 /** Matches a UiElement that is 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 */ 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 */ 148 public static ChainFinder chain(Finder first, Finder second) { 149 return new ChainFinder(first, second); 150 } 151 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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