/* * Copyright (C) 2013 DroidDriver committers * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.appium.droiddriver.finders; import android.content.Context; import java.util.ArrayList; import java.util.List; import io.appium.droiddriver.UiElement; import io.appium.droiddriver.exceptions.ElementNotFoundException; import io.appium.droiddriver.util.InstrumentationUtils; import static io.appium.droiddriver.util.Preconditions.checkNotNull; /** * Convenience methods to create commonly used finders. */ public class By { private static final MatchFinder ANY = new MatchFinder(null); /** * Matches any UiElement. */ public static MatchFinder any() { return ANY; } /** * Matches a UiElement whose {@code attribute} is {@code true}. */ public static MatchFinder is(Attribute attribute) { return new MatchFinder(Predicates.attributeTrue(attribute)); } /** * Matches a UiElement whose {@code attribute} is {@code false} or is not set. */ public static MatchFinder not(Attribute attribute) { return new MatchFinder(Predicates.attributeFalse(attribute)); } /** * Matches a UiElement by a resource id defined in the AUT. */ public static MatchFinder resourceId(int resourceId) { Context targetContext = InstrumentationUtils.getInstrumentation().getTargetContext(); return resourceId(targetContext.getResources().getResourceName(resourceId)); } /** * Matches a UiElement by the string representation of a resource id. This works for resources not * belonging to the AUT. */ public static MatchFinder resourceId(String resourceId) { return new MatchFinder(Predicates.attributeEquals(Attribute.RESOURCE_ID, resourceId)); } /** * Matches a UiElement by package name. */ public static MatchFinder packageName(String name) { return new MatchFinder(Predicates.attributeEquals(Attribute.PACKAGE, name)); } /** * Matches a UiElement by the exact text. */ public static MatchFinder text(String text) { return new MatchFinder(Predicates.attributeEquals(Attribute.TEXT, text)); } /** * Matches a UiElement whose text matches {@code regex}. */ public static MatchFinder textRegex(String regex) { return new MatchFinder(Predicates.attributeMatches(Attribute.TEXT, regex)); } /** * Matches a UiElement whose text contains {@code substring}. */ public static MatchFinder textContains(String substring) { return new MatchFinder(Predicates.attributeContains(Attribute.TEXT, substring)); } /** * Matches a UiElement by content description. */ public static MatchFinder contentDescription(String contentDescription) { return new MatchFinder(Predicates.attributeEquals(Attribute.CONTENT_DESC, contentDescription)); } /** * Matches a UiElement whose content description contains {@code substring}. */ public static MatchFinder contentDescriptionContains(String substring) { return new MatchFinder(Predicates.attributeContains(Attribute.CONTENT_DESC, substring)); } /** * Matches a UiElement by class name. */ public static MatchFinder className(String className) { return new MatchFinder(Predicates.attributeEquals(Attribute.CLASS, className)); } /** * Matches a UiElement by class name. */ public static MatchFinder className(Class clazz) { return className(clazz.getName()); } /** * Matches a UiElement that is selected. */ public static MatchFinder selected() { return is(Attribute.SELECTED); } /** * Matches by XPath. When applied on an non-root element, it will not evaluate above the context * element.

XPath is the domain-specific-language for navigating a node tree. It is ideal if * the UiElement to match has a complex relationship with surrounding nodes. For simple cases, * {@link #withParent} or {@link #withAncestor} are preferred, which can combine with other {@link * MatchFinder}s in {@link #allOf}. For complex cases like below, XPath is superior: * *

   * {@code
   * 
   *   
   *     
   *     
   *   
   *   
   *     
   *     
   *   
   * 
   * 
   * }
   * 
* * If we need to locate the RelativeLayout containing the album "Forever" instead of a song or an * artist named "Forever", this XPath works: * *
   * {@code //*[LinearLayout/*[@text='Albums']]/RelativeLayout[*[@text='Forever']]}
   * 
* * @param xPath The xpath to use * @return a finder which locates elements via XPath */ public static ByXPath xpath(String xPath) { return new ByXPath(xPath); } /** * Returns a finder that uses the UiElement returned by first Finder as context for the second * Finder.

typically first Finder finds the ancestor, then second Finder finds the target * UiElement, which is a descendant.

Note that if the first Finder matches multiple * UiElements, only the first match is tried, which usually is not what callers expect. In this * case, allOf(second, withAncesor(first)) may work. */ public static ChainFinder chain(Finder first, Finder second) { return new ChainFinder(first, second); } private static List> getPredicates(MatchFinder... finders) { ArrayList> predicates = new ArrayList<>(finders.length); for (int i = 0; i < finders.length; i++) { predicates.add(finders[i].predicate); } return predicates; } /** * Evaluates given {@code finders} in short-circuit fashion in the order they are passed. Costly * finders (for example those returned by with* methods that navigate the node tree) should be * passed after cheap finders (for example the ByAttribute finders). * * @return a finder that is the logical conjunction of given finders */ public static MatchFinder allOf(final MatchFinder... finders) { return new MatchFinder(Predicates.allOf(getPredicates(finders))); } /** * Evaluates given {@code finders} in short-circuit fashion in the order they are passed. Costly * finders (for example those returned by with* methods that navigate the node tree) should be * passed after cheap finders (for example the ByAttribute finders). * * @return a finder that is the logical disjunction of given finders */ public static MatchFinder anyOf(final MatchFinder... finders) { return new MatchFinder(Predicates.anyOf(getPredicates(finders))); } /** * Matches a UiElement whose parent matches the given parentFinder. For complex cases, consider * {@link #xpath}. */ public static MatchFinder withParent(MatchFinder parentFinder) { checkNotNull(parentFinder); return new MatchFinder(Predicates.withParent(parentFinder.predicate)); } /** * Matches a UiElement whose ancestor matches the given ancestorFinder. For complex cases, * consider {@link #xpath}. */ public static MatchFinder withAncestor(MatchFinder ancestorFinder) { checkNotNull(ancestorFinder); return new MatchFinder(Predicates.withAncestor(ancestorFinder.predicate)); } /** * Matches a UiElement which has a visible sibling matching the given siblingFinder. This could be * inefficient; consider {@link #xpath}. */ public static MatchFinder withSibling(MatchFinder siblingFinder) { checkNotNull(siblingFinder); return new MatchFinder(Predicates.withSibling(siblingFinder.predicate)); } /** * Matches a UiElement which has a visible child matching the given childFinder. This could be * inefficient; consider {@link #xpath}. */ public static MatchFinder withChild(MatchFinder childFinder) { checkNotNull(childFinder); return new MatchFinder(Predicates.withChild(childFinder.predicate)); } /** * Matches a UiElement whose descendant (including self) matches the given descendantFinder. This * could be VERY inefficient; consider {@link #xpath}. */ public static MatchFinder withDescendant(final MatchFinder descendantFinder) { checkNotNull(descendantFinder); return new MatchFinder(new Predicate() { @Override public boolean apply(UiElement element) { try { descendantFinder.find(element); return true; } catch (ElementNotFoundException enfe) { return false; } } @Override public String toString() { return "withDescendant(" + descendantFinder + ")"; } }); } /** * Matches a UiElement that does not match the provided {@code finder}. */ public static MatchFinder not(MatchFinder finder) { checkNotNull(finder); return new MatchFinder(Predicates.not(finder.predicate)); } private By() { } }