diff --git a/docs/Page-objects.md b/docs/Page-objects.md index 9a34063ca..7c41cc612 100644 --- a/docs/Page-objects.md +++ b/docs/Page-objects.md @@ -145,7 +145,7 @@ RemoteWebElement someElement; List someElements; ``` -## Also possible combined variants: +## Also possible combined variants for target platforms: ```java import org.openqa.selenium.remote.RemoteWebElement; @@ -194,6 +194,67 @@ RemoteWebElement someElement; List someElements; ``` +## Mixed chain/any locator strategy + +Some locator-element could not be defined certainly sometimes. It may be defined as one of possible variants/chained locator. +If the using of _xpath_ is not convenient for some reasons so there are possible use cases + +### the chained searching + +```java +import org.openqa.selenium.remote.RemoteWebElement; +import io.appium.java_client.pagefactory.*; +import org.openqa.selenium.support.FindBys; +import org.openqa.selenium.support.FindBy; + +//it is necessary to define priorities at this case. The lower number means the higher priority. +//The default value is 0 (the highest priority) +@iOSFindBy(someStrategy1) +@iOSFindAll(value = {@iOSBy(subloctor1), @iOSBy(subloctor1)}, priority = 1) //it means that the chained searching contains +//the searching by all possible locators as the element of the chain +@iOSFindBy(someStrategy2, priority = 2) +@iOSFindBy(someStrategy3, priority = 3) +RemoteWebElement someElement; + + +@iOSFindBy(someStrategy1) +@iOSFindAll(value = {@iOSBy(subloctor1), @iOSBy(subloctor1)}, priority = 1) //it means that the chained searching contains +//the searching by all possible locators as the element of the chain +@iOSFindBy(someStrategy2, priority = 2) +@iOSFindBy(someStrategy3, priority = 3) +List someElements; +``` + +### all possible + +```java +import org.openqa.selenium.remote.RemoteWebElement; +import io.appium.java_client.pagefactory.*; +import org.openqa.selenium.support.FindBys; +import org.openqa.selenium.support.FindBy; + +//it is not necessary to define priorities at this case. But it can manage the searching. +//The lower number means the higher priority. +//The default value is 0 (the highest priority) +@HowToUseLocators(iOSAutomation = ALL_POSSIBLE) +@iOSFindBy(someStrategy1) +@iOSFindBys(value = {@iOSBy(subloctor1), @iOSBy(subloctor1)}, priority = 1) //it means that the searching by all possible locators +// contains the searching by chained locator as one of all possible variants +@iOSFindBy(someStrategy2, priority = 2) +@iOSFindBy(someStrategy3, priority = 3) +RemoteWebElement someElement; + + + +@HowToUseLocators(iOSAutomation = ALL_POSSIBLE) +@iOSFindBy(someStrategy1) +@iOSFindBys(value = {@iOSBy(subloctor1), @iOSBy(subloctor1)}, priority = 1) //it means that the searching by all possible locators +// contains the searching by chained locator as one of all possible variants +@iOSFindBy(someStrategy2, priority = 2) +@iOSFindBy(someStrategy3, priority = 3) +List someElements; +``` + # Appium Java client is integrated with Selenium PageFactory by AppiumFieldDecorator. Object fields are populated as below: diff --git a/google-style.xml b/google-style.xml index 6f3440428..06e2c452c 100755 --- a/google-style.xml +++ b/google-style.xml @@ -97,7 +97,7 @@ value="Package name ''{0}'' must match pattern ''{1}''."/> - + diff --git a/src/main/java/io/appium/java_client/ScreenshotState.java b/src/main/java/io/appium/java_client/ScreenshotState.java index d698819b1..1c8675eb7 100644 --- a/src/main/java/io/appium/java_client/ScreenshotState.java +++ b/src/main/java/io/appium/java_client/ScreenshotState.java @@ -16,6 +16,8 @@ package io.appium.java_client; +import static nu.pattern.OpenCV.loadShared; + import org.opencv.core.Core; import org.opencv.core.CvType; import org.opencv.core.Mat; @@ -32,8 +34,6 @@ import java.util.function.Function; import java.util.function.Supplier; -import static nu.pattern.OpenCV.loadShared; - public class ScreenshotState { private static final Duration DEFAULT_INTERVAL_MS = Duration.ofMillis(500); diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidBy.java b/src/main/java/io/appium/java_client/pagefactory/AndroidBy.java new file mode 100644 index 000000000..b802cbbbc --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidBy.java @@ -0,0 +1,61 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.java_client.pagefactory; + +/** + * Used to build a complex android automator locator. + */ +public @interface AndroidBy { + /** + * It is an is Android UIAutomator string. + * Read http://developer.android.com/intl/ru/tools/testing-support-library/ + * index.html#uia-apis + */ + String uiAutomator() default ""; + + /** + * It an UI automation accessibility Id which is a convenient to Android. + * About Android accessibility + * https://developer.android.com/intl/ru/training/accessibility/accessible-app.html + */ + String accessibility() default ""; + + /** + * It is an id of the target element. + */ + String id() default ""; + + /** + * It is a className of the target element. + */ + String className() default ""; + + /** + * It is a desired element tag. + */ + String tagName() default ""; + + /** + * It is a xpath to the target element. + */ + String xpath() default ""; + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; +} diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidFindAll.java b/src/main/java/io/appium/java_client/pagefactory/AndroidFindAll.java index 3f3327ee4..1f411a4a5 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AndroidFindAll.java +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindAll.java @@ -16,27 +16,31 @@ package io.appium.java_client.pagefactory; -import java.lang.annotation.ElementType; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Used to mark a field on a Page/Screen Object to indicate that lookup should use a - * series of {@link io.appium.java_client.pagefactory.AndroidFindBy} tags + * series of {@link io.appium.java_client.pagefactory.AndroidBy} tags * It will then search for all elements that match any criteria. Note that elements * are not guaranteed to be in document order. - * It is deprecated. Set of {@link io.appium.java_client.pagefactory.AndroidFindBy} - * can be defined without this annotation. To define the correct way how to use - * the defined set please take a look at {@link HowToUseLocators}. The article. - * https://docs.oracle.com/javase/tutorial/java/annotations/repeating.html. */ -@Deprecated -@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RUNTIME) @Target({FIELD, TYPE}) +@Repeatable(AndroidFindByAllSet.class) public @interface AndroidFindAll { /** - * It is a set of {@link io.appium.java_client.pagefactory.AndroidFindBy} strategies which may + * It is a set of {@link io.appium.java_client.pagefactory.AndroidBy} strategies which may * be used to find the target element. */ - AndroidFindBy[] value(); + AndroidBy[] value(); + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; } diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java index 501cca7c7..bb17d861b 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java @@ -16,10 +16,12 @@ package io.appium.java_client.pagefactory; -import java.lang.annotation.ElementType; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -30,7 +32,7 @@ * this allows users to quickly and easily create PageObjects. * using Android UI selectors, accessibility, id, name, class name, tag and xpath */ -@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RUNTIME) @Target({FIELD, TYPE}) @Repeatable(AndroidFindBySet.class) public @interface AndroidFindBy { /** @@ -66,4 +68,9 @@ * It is a xpath to the target element. */ String xpath() default ""; + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; } diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidFindByAllSet.java b/src/main/java/io/appium/java_client/pagefactory/AndroidFindByAllSet.java new file mode 100644 index 000000000..40e49415a --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindByAllSet.java @@ -0,0 +1,22 @@ +package io.appium.java_client.pagefactory; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Defines set of chained/possible locators. Each one locator + * should be defined with {@link AndroidFindAll} + */ +@Target(value = {TYPE, FIELD}) +@Retention(value = RUNTIME) +public @interface AndroidFindByAllSet { + /** + * @return an array of {@link AndroidFindAll} which builds a sequence of + * the chained searching for elements or a set of possible locators + */ + AndroidFindAll[] value(); +} diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidFindByChainSet.java b/src/main/java/io/appium/java_client/pagefactory/AndroidFindByChainSet.java new file mode 100644 index 000000000..bf1958767 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindByChainSet.java @@ -0,0 +1,22 @@ +package io.appium.java_client.pagefactory; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Defines set of chained/possible locators. Each one locator + * should be defined with {@link io.appium.java_client.pagefactory.AndroidFindBys} + */ +@Target(value = {TYPE, FIELD}) +@Retention(value = RUNTIME) +public @interface AndroidFindByChainSet { + /** + * @return an array of {@link io.appium.java_client.pagefactory.AndroidFindBys} which builds a sequence of + * the chained searching for elements or a set of possible locators + */ + AndroidFindBys[] value(); +} diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBySet.java b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBySet.java index 863b619d0..08be7d053 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBySet.java +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBySet.java @@ -16,17 +16,19 @@ package io.appium.java_client.pagefactory; -import java.lang.annotation.ElementType; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Defines set of chained/possible locators. Each one locator * should be defined with {@link io.appium.java_client.pagefactory.AndroidFindBy} */ -@Target(value = {ElementType.TYPE, ElementType.FIELD}) -@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = {TYPE, FIELD}) +@Retention(value = RUNTIME) public @interface AndroidFindBySet { /** * @return an array of {@link io.appium.java_client.pagefactory.AndroidFindBy} which builds a sequence of diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBys.java b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBys.java index 692d64da2..e8c0d2ae1 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBys.java +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBys.java @@ -16,25 +16,29 @@ package io.appium.java_client.pagefactory; -import java.lang.annotation.ElementType; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Used to mark a field on a Page Object to indicate that lookup should use - * a series of {@link io.appium.java_client.pagefactory.AndroidFindBy} tags. - * It is deprecated. Set of {@link io.appium.java_client.pagefactory.AndroidFindBy} - * can be defined without this annotation. To define the correct way how to use - * the defined set please take a look at {@link HowToUseLocators}. The article. - * https://docs.oracle.com/javase/tutorial/java/annotations/repeating.html. + * a series of {@link io.appium.java_client.pagefactory.AndroidBy} tags. */ -@Deprecated -@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RUNTIME) @Target({FIELD, TYPE}) +@Repeatable(AndroidFindByChainSet.class) public @interface AndroidFindBys { /** - * It is a set of {@link io.appium.java_client.pagefactory.AndroidFindBy} strategies which build + * It is a set of {@link io.appium.java_client.pagefactory.AndroidBy} strategies which build * the chain of the searching for the target element. */ - AndroidFindBy[] value(); + AndroidBy[] value(); + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; } diff --git a/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java b/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java index 7b8daf458..24a3fd581 100644 --- a/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java +++ b/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java @@ -16,9 +16,14 @@ package io.appium.java_client.pagefactory; +import static java.lang.Integer.signum; +import static java.util.Arrays.asList; +import static java.util.Optional.ofNullable; + import io.appium.java_client.pagefactory.bys.ContentMappedBy; import io.appium.java_client.pagefactory.bys.ContentType; import io.appium.java_client.pagefactory.bys.builder.AppiumByBuilder; +import io.appium.java_client.pagefactory.bys.builder.ByChained; import io.appium.java_client.pagefactory.bys.builder.HowToUseSelectors; import org.openqa.selenium.By; import org.openqa.selenium.support.ByIdOrName; @@ -26,67 +31,57 @@ import org.openqa.selenium.support.FindAll; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBys; +import org.openqa.selenium.support.pagefactory.ByAll; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; public class DefaultElementByBuilder extends AppiumByBuilder { + private static final String PRIORITY = "priority"; + private static final String VALUE = "value"; + private static final Class[] ANNOTATION_ARGUMENTS = new Class[]{}; + private static final Object[] ANNOTATION_PARAMETERS = new Object[]{}; + public DefaultElementByBuilder(String platform, String automation) { super(platform, automation); } private static void checkDisallowedAnnotationPairs(Annotation a1, Annotation a2) - throws IllegalArgumentException { + throws IllegalArgumentException { if (a1 != null && a2 != null) { throw new IllegalArgumentException( - "If you use a '@" + a1.getClass().getSimpleName() + "' annotation, " - + "you must not also use a '@" + a2.getClass().getSimpleName() - + "' annotation"); + "If you use a '@" + a1.getClass().getSimpleName() + "' annotation, " + + "you must not also use a '@" + a2.getClass().getSimpleName() + + "' annotation"); } } - private static By buildMobileBy(LocatorGroupStrategy locatorGroupStrategy, Annotation[] annotations) { - if (annotations.length == 1) { - return createBy(new Annotation[] {annotations[0]}, HowToUseSelectors.USE_ONE); - } else { - LocatorGroupStrategy strategy = Optional.ofNullable(locatorGroupStrategy) - .orElse(LocatorGroupStrategy.CHAIN); - if (strategy.equals(LocatorGroupStrategy.ALL_POSSIBLE)) { - return createBy(annotations, HowToUseSelectors.USE_ANY); - } - return createBy(annotations, HowToUseSelectors.BUILD_CHAINED); + private static By buildMobileBy(LocatorGroupStrategy locatorGroupStrategy, By[] bys) { + if (bys.length == 0) { + return null; } + LocatorGroupStrategy strategy = ofNullable(locatorGroupStrategy) + .orElse(LocatorGroupStrategy.CHAIN); + if (strategy.equals(LocatorGroupStrategy.ALL_POSSIBLE)) { + return new ByAll(bys); + } + return new ByChained(bys); } - @Override protected void assertValidAnnotations() { + @Override + protected void assertValidAnnotations() { AnnotatedElement annotatedElement = annotatedElementContainer.getAnnotated(); - AndroidFindBy androidBy = annotatedElement.getAnnotation(AndroidFindBy.class); - AndroidFindBys androidBys = annotatedElement.getAnnotation(AndroidFindBys.class); - checkDisallowedAnnotationPairs(androidBy, androidBys); - AndroidFindAll androidFindAll = annotatedElement.getAnnotation(AndroidFindAll.class); - checkDisallowedAnnotationPairs(androidBy, androidFindAll); - checkDisallowedAnnotationPairs(androidBys, androidFindAll); - - SelendroidFindBy selendroidBy = annotatedElement.getAnnotation(SelendroidFindBy.class); - SelendroidFindBys selendroidBys = annotatedElement.getAnnotation(SelendroidFindBys.class); - checkDisallowedAnnotationPairs(selendroidBy, selendroidBys); - SelendroidFindAll selendroidFindAll = - annotatedElement.getAnnotation(SelendroidFindAll.class); - checkDisallowedAnnotationPairs(selendroidBy, selendroidFindAll); - checkDisallowedAnnotationPairs(selendroidBys, selendroidFindAll); - - iOSFindBy iOSBy = annotatedElement.getAnnotation(iOSFindBy.class); - iOSFindBys iOSBys = annotatedElement.getAnnotation(iOSFindBys.class); - checkDisallowedAnnotationPairs(iOSBy, iOSBys); - iOSFindAll iOSFindAll = annotatedElement.getAnnotation(iOSFindAll.class); - checkDisallowedAnnotationPairs(iOSBy, iOSFindAll); - checkDisallowedAnnotationPairs(iOSBys, iOSFindAll); - FindBy findBy = annotatedElement.getAnnotation(FindBy.class); FindBys findBys = annotatedElement.getAnnotation(FindBys.class); checkDisallowedAnnotationPairs(findBy, findBys); @@ -95,7 +90,8 @@ private static By buildMobileBy(LocatorGroupStrategy locatorGroupStrategy, Annot checkDisallowedAnnotationPairs(findBys, findAll); } - @Override protected By buildDefaultBy() { + @Override + protected By buildDefaultBy() { AnnotatedElement annotatedElement = annotatedElementContainer.getAnnotated(); By defaultBy = null; FindBy findBy = annotatedElement.getAnnotation(FindBy.class); @@ -119,108 +115,88 @@ private static By buildMobileBy(LocatorGroupStrategy locatorGroupStrategy, Annot return defaultBy; } - @Override protected By buildMobileNativeBy() { + private By[] getBys(Class singleLocator, Class chainedLocator, + Class allLocator) { + AnnotationComparator comparator = new AnnotationComparator(); AnnotatedElement annotatedElement = annotatedElementContainer.getAnnotated(); - HowToUseLocators howToUseLocators = annotatedElement.getAnnotation(HowToUseLocators.class); - if (isSelendroidAutomation()) { - SelendroidFindBy[] selendroidFindByArray = - annotatedElement.getAnnotationsByType(SelendroidFindBy.class); - //should be kept for some time - SelendroidFindBys selendroidFindBys = - annotatedElement.getAnnotation(SelendroidFindBys.class); - SelendroidFindAll selendroidFindByAll = - annotatedElement.getAnnotation(SelendroidFindAll.class); - - if (selendroidFindByArray != null && selendroidFindByArray.length == 1) { - return createBy(new Annotation[] {selendroidFindByArray[0]}, HowToUseSelectors.USE_ONE); + List annotations = new ArrayList<>(asList(annotatedElement.getAnnotationsByType(singleLocator))); + annotations.addAll(asList(annotatedElement.getAnnotationsByType(chainedLocator))); + annotations.addAll(asList(annotatedElement.getAnnotationsByType(allLocator))); + + annotations.sort(comparator); + List result = new ArrayList<>(); + + for (Annotation a : annotations) { + Class annotationClass = a.annotationType(); + if (singleLocator.equals(annotationClass)) { + result.add(createBy(new Annotation[]{a}, HowToUseSelectors.USE_ONE)); + continue; } - if (selendroidFindBys != null) { - return createBy(selendroidFindBys.value(), HowToUseSelectors.BUILD_CHAINED); + Method value; + Annotation[] subLocators; + try { + value = annotationClass.getMethod(VALUE, ANNOTATION_ARGUMENTS); + subLocators = (Annotation[]) value.invoke(a, ANNOTATION_PARAMETERS); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new ClassCastException(String.format("The annotation '%s' has no convenient '%s' method which " + + "returns array of annotations", annotationClass.getName(), VALUE)); } - if (selendroidFindByAll != null) { - return createBy(selendroidFindByAll.value(), HowToUseSelectors.USE_ANY); + Arrays.sort(subLocators, comparator); + if (chainedLocator.equals(annotationClass)) { + result.add(createBy(subLocators, HowToUseSelectors.BUILD_CHAINED)); + continue; } - /////////////////////////////////////// - //code that supposed to be supported - if (selendroidFindByArray != null && selendroidFindByArray.length > 0) { - return buildMobileBy(howToUseLocators != null ? howToUseLocators.selendroidAutomation() : null, - selendroidFindByArray); + + if (allLocator.equals(annotationClass)) { + result.add(createBy(subLocators, HowToUseSelectors.USE_ANY)); } } - if (isAndroid()) { - AndroidFindBy[] androidFindByArray = annotatedElement.getAnnotationsByType(AndroidFindBy.class); - //should be kept for some time - AndroidFindBys androidFindBys = annotatedElement.getAnnotation(AndroidFindBys.class); - AndroidFindAll androidFindAll = annotatedElement.getAnnotation(AndroidFindAll.class); + return result.toArray(new By[result.size()]); + } - if (androidFindByArray != null && androidFindByArray.length == 1) { - return createBy(new Annotation[] {androidFindByArray[0]}, HowToUseSelectors.USE_ONE); - } + @Override + protected By buildMobileNativeBy() { + AnnotatedElement annotatedElement = annotatedElementContainer.getAnnotated(); + HowToUseLocators howToUseLocators = annotatedElement.getAnnotation(HowToUseLocators.class); - if (androidFindBys != null) { - return createBy(androidFindBys.value(), HowToUseSelectors.BUILD_CHAINED); - } + Optional howToUseLocatorsOptional = ofNullable(howToUseLocators); - if (androidFindAll != null) { - return createBy(androidFindAll.value(), HowToUseSelectors.USE_ANY); - } - /////////////////////////////////////// - //code that supposed to be supported - if (androidFindByArray != null && androidFindByArray.length > 0) { - return buildMobileBy(howToUseLocators != null ? howToUseLocators.androidAutomation() : null, - androidFindByArray); - } + By result = null; + if (isSelendroidAutomation()) { + result = buildMobileBy(howToUseLocatorsOptional + .map(HowToUseLocators::selendroidAutomation).orElse(null), + getBys(SelendroidFindBy.class, SelendroidFindBys.class, SelendroidFindAll.class)); } - if (isIOSXcuit()) { - iOSXCUITFindBy[] xCuitFindByArray = annotatedElement.getAnnotationsByType(iOSXCUITFindBy.class); - if (xCuitFindByArray != null && xCuitFindByArray.length > 0) { - return buildMobileBy(howToUseLocators != null ? howToUseLocators.iOSXCUITAutomation() : null, - xCuitFindByArray); - } + if (isAndroid() && result == null) { + return buildMobileBy(howToUseLocatorsOptional.map(HowToUseLocators::androidAutomation).orElse(null), + getBys(AndroidFindBy.class, AndroidFindBys.class, AndroidFindAll.class)); } - if (isIOS()) { - iOSFindBy[] iOSFindByArray = annotatedElement.getAnnotationsByType(iOSFindBy.class); - //should be kept for some time - iOSFindBys iOSFindBys = annotatedElement.getAnnotation(iOSFindBys.class); - iOSFindAll iOSFindAll = annotatedElement.getAnnotation(iOSFindAll.class); - - if (iOSFindByArray != null && iOSFindByArray.length == 1) { - return createBy(new Annotation[] {iOSFindByArray[0]}, HowToUseSelectors.USE_ONE); - } - - if (iOSFindBys != null) { - return createBy(iOSFindBys.value(), HowToUseSelectors.BUILD_CHAINED); - } + if (isIOSXcuit()) { + result = buildMobileBy(howToUseLocatorsOptional.map(HowToUseLocators::iOSXCUITAutomation).orElse(null), + getBys(iOSXCUITFindBy.class, iOSXCUITFindBys.class, iOSXCUITFindAll.class)); + } - if (iOSFindAll != null) { - return createBy(iOSFindAll.value(), HowToUseSelectors.USE_ANY); - } - /////////////////////////////////////// - //code that supposed to be supported - if (iOSFindByArray != null && iOSFindByArray.length > 0) { - return buildMobileBy(howToUseLocators != null ? howToUseLocators.iOSAutomation() : null, - iOSFindByArray); - } + if (isIOS() && result == null) { + return buildMobileBy(howToUseLocatorsOptional.map(HowToUseLocators::iOSAutomation).orElse(null), + getBys(iOSFindBy.class, iOSFindBys.class, iOSFindAll.class)); } if (isWindows()) { - WindowsFindBy[] windowsFindByArray = annotatedElement.getAnnotationsByType(WindowsFindBy.class); - if (windowsFindByArray != null && windowsFindByArray.length > 0) { - return buildMobileBy(howToUseLocators != null ? howToUseLocators.windowsAutomation() : null, - windowsFindByArray); - } + return buildMobileBy(howToUseLocatorsOptional.map(HowToUseLocators::windowsAutomation).orElse(null), + getBys(WindowsFindBy.class, WindowsFindBys.class, WindowsFindAll.class)); } - return null; + return ofNullable(result).orElse(null); } - @Override public boolean isLookupCached() { + @Override + public boolean isLookupCached() { AnnotatedElement annotatedElement = annotatedElementContainer.getAnnotated(); return (annotatedElement.getAnnotation(CacheLookup.class) != null); } @@ -232,7 +208,8 @@ private By returnMappedBy(By byDefault, By nativeAppBy) { return new ContentMappedBy(contentMap); } - @Override public By buildBy() { + @Override + public By buildBy() { assertValidAnnotations(); By defaultBy = buildDefaultBy(); @@ -242,14 +219,14 @@ private By returnMappedBy(By byDefault, By nativeAppBy) { if (defaultBy == null && mobileNativeBy == null) { defaultBy = - new ByIdOrName(((Field) annotatedElementContainer.getAnnotated()).getName()); + new ByIdOrName(((Field) annotatedElementContainer.getAnnotated()).getName()); mobileNativeBy = new By.ById(idOrName); return returnMappedBy(defaultBy, mobileNativeBy); } if (defaultBy == null) { defaultBy = - new ByIdOrName(((Field) annotatedElementContainer.getAnnotated()).getName()); + new ByIdOrName(((Field) annotatedElementContainer.getAnnotated()).getName()); return returnMappedBy(defaultBy, mobileNativeBy); } @@ -260,4 +237,39 @@ private By returnMappedBy(By byDefault, By nativeAppBy) { return returnMappedBy(defaultBy, mobileNativeBy); } + + private static class AnnotationComparator implements Comparator { + + private static Method getPriorityMethod(Class clazz) { + try { + return clazz.getMethod(PRIORITY, ANNOTATION_ARGUMENTS); + } catch (NoSuchMethodException e) { + throw new ClassCastException(String.format("Class %s has no '%s' method", clazz.getName(), PRIORITY)); + } + } + + private static int getPriorityValue(Method priorityMethod, Annotation annotation, + Class clazz) { + try { + return (int) priorityMethod.invoke(annotation, ANNOTATION_PARAMETERS); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new IllegalArgumentException(String + .format("It is impossible to get priority. Annotation class: %s", clazz.toString()), e); + } + } + + @Override + public int compare(Annotation o1, Annotation o2) { + Class c1 = o1.annotationType(); + Class c2 = o2.annotationType(); + + Method priority1 = getPriorityMethod(c1); + Method priority2 = getPriorityMethod(c2); + + int p1 = getPriorityValue(priority1, o1, c1); + int p2 = getPriorityValue(priority2, o2, c2); + + return signum(p1 - p2); + } + } } \ No newline at end of file diff --git a/src/main/java/io/appium/java_client/pagefactory/SelendroidBy.java b/src/main/java/io/appium/java_client/pagefactory/SelendroidBy.java new file mode 100644 index 000000000..75933eba4 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/SelendroidBy.java @@ -0,0 +1,63 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.java_client.pagefactory; + + +/** + * Used to build a complex selendroid locator. + */ +public @interface SelendroidBy { + /** + * It is an id of the target element. + */ + String id() default ""; + + /** + * It is used in Selendroid mode instead of accessibility id. + */ + String name() default ""; + + /** + * It is a className of the target element. + */ + String className() default ""; + + /** + * It is a desired element tag. + */ + String tagName() default ""; + + /** + * It is a xpath to the target element. + */ + String xpath() default ""; + + /** + * It is a text of the desired element. + */ + String linkText() default ""; + + /** + * It is a part of the text of the desired element. + */ + String partialLinkText() default ""; + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; +} diff --git a/src/main/java/io/appium/java_client/pagefactory/SelendroidFindAll.java b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindAll.java index b9d67dd62..8e2e56a79 100644 --- a/src/main/java/io/appium/java_client/pagefactory/SelendroidFindAll.java +++ b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindAll.java @@ -16,27 +16,29 @@ package io.appium.java_client.pagefactory; -import java.lang.annotation.ElementType; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Used to mark a field on a Page/Screen Object to indicate that lookup should use a - * series of {@link io.appium.java_client.pagefactory.SelendroidFindBy} tags + * series of {@link io.appium.java_client.pagefactory.SelendroidBy} tags * It will then search for all elements that match any criteria. Note that elements * are not guaranteed to be in document order. - * It is deprecated. Set of {@link io.appium.java_client.pagefactory.SelendroidFindBy} - * can be defined without this annotation. To define the correct way how to use - * the defined set please take a look at {@link HowToUseLocators}. The article. - * https://docs.oracle.com/javase/tutorial/java/annotations/repeating.html. */ -@Deprecated -@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RUNTIME) @Target({FIELD, TYPE}) public @interface SelendroidFindAll { /** - * It is a set of {@link io.appium.java_client.pagefactory.SelendroidFindBy} strategies which + * It is a set of {@link io.appium.java_client.pagefactory.SelendroidBy} strategies which * may be used to find the target element. */ - SelendroidFindBy[] value(); + SelendroidBy[] value(); + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; } diff --git a/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBy.java b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBy.java index be0cd7a10..146079a14 100644 --- a/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBy.java @@ -16,10 +16,12 @@ package io.appium.java_client.pagefactory; -import java.lang.annotation.ElementType; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -30,7 +32,7 @@ * this allows users to quickly and easily create PageObjects. * using Selendroid UI selectors like, id, name, class name, tag and xpath */ -@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RUNTIME) @Target({FIELD, TYPE}) @Repeatable(SelendroidFindBySet.class) public @interface SelendroidFindBy { /** @@ -67,4 +69,9 @@ * It is a part of the text of the desired element. */ String partialLinkText() default ""; + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; } diff --git a/src/main/java/io/appium/java_client/pagefactory/SelendroidFindByAllSet.java b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindByAllSet.java new file mode 100644 index 000000000..0b83c223c --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindByAllSet.java @@ -0,0 +1,38 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.java_client.pagefactory; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Defines set of chained/possible locators. Each one locator + * should be defined with {@link SelendroidFindAll} + */ +@Target(value = {TYPE, FIELD}) +@Retention(value = RUNTIME) +public @interface SelendroidFindByAllSet { + /** + * @return an array of {@link SelendroidFindAll} which builds a sequence of + * the chained searching for elements or a set of possible locators + */ + SelendroidFindAll[] value(); +} diff --git a/src/main/java/io/appium/java_client/pagefactory/SelendroidFindByChainSet.java b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindByChainSet.java new file mode 100644 index 000000000..5824ed173 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindByChainSet.java @@ -0,0 +1,38 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.java_client.pagefactory; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Defines set of chained/possible locators. Each one locator + * should be defined with {@link SelendroidFindBys} + */ +@Target(value = {TYPE, FIELD}) +@Retention(value = RUNTIME) +public @interface SelendroidFindByChainSet { + /** + * @return an array of {@link SelendroidFindBys} which builds a sequence of + * the chained searching for elements or a set of possible locators + */ + SelendroidFindBys[] value(); +} diff --git a/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBySet.java b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBySet.java index 0fa8fa721..38e903966 100644 --- a/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBySet.java +++ b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBySet.java @@ -16,17 +16,19 @@ package io.appium.java_client.pagefactory; -import java.lang.annotation.ElementType; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Defines set of chained/possible locators. Each one locator * should be defined with {@link io.appium.java_client.pagefactory.SelendroidFindBy} */ -@Target(value = {ElementType.TYPE, ElementType.FIELD}) -@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = {TYPE, FIELD}) +@Retention(value = RUNTIME) public @interface SelendroidFindBySet { /** * @return an array of {@link io.appium.java_client.pagefactory.SelendroidFindBy} which builds a sequence of diff --git a/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBys.java b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBys.java index b0d7d9deb..6a7bd2f3a 100644 --- a/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBys.java +++ b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBys.java @@ -16,25 +16,27 @@ package io.appium.java_client.pagefactory; -import java.lang.annotation.ElementType; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Used to mark a field on a Page Object to indicate that lookup should - * use a series of {@link io.appium.java_client.pagefactory.SelendroidFindBy} tags. - * It is deprecated. Set of {@link io.appium.java_client.pagefactory.SelendroidFindBy} - * can be defined without this annotation. To define the correct way how to use - * the defined set please take a look at {@link HowToUseLocators}. The article. - * https://docs.oracle.com/javase/tutorial/java/annotations/repeating.html. + * use a series of {@link io.appium.java_client.pagefactory.SelendroidBy} tags. */ -@Deprecated -@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RUNTIME) @Target({FIELD, TYPE}) public @interface SelendroidFindBys { /** - * It is a set of {@link io.appium.java_client.pagefactory.SelendroidFindBy} strategies which + * It is a set of {@link io.appium.java_client.pagefactory.SelendroidBy} strategies which * build the chain of the searching for the target element. */ - SelendroidFindBy[] value(); + SelendroidBy[] value(); + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; } diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsBy.java b/src/main/java/io/appium/java_client/pagefactory/WindowsBy.java new file mode 100644 index 000000000..892201cc6 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsBy.java @@ -0,0 +1,58 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.java_client.pagefactory; + +/** + * Used to build a complex Windows automation locator. + */ +public @interface WindowsBy { + + /** + * It is an is Windows automator string. + */ + String windowsAutomation() default ""; + + /** + * It an UI automation accessibility Id which is a convenient to Windows. + */ + String accessibility() default ""; + + /** + * It is an id of the target element. + */ + String id() default ""; + + /** + * It is a className of the target element. + */ + String className() default ""; + + /** + * It is a desired element tag. + */ + String tagName() default ""; + + /** + * It is a xpath to the target element. + */ + String xpath() default ""; + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; +} diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindAll.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindAll.java new file mode 100644 index 000000000..5b1c7cf00 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindAll.java @@ -0,0 +1,46 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.java_client.pagefactory; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Used to mark a field on a Page/Screen Object to indicate that lookup should use a series + * of {@link WindowsBy} tags + * It will then search for all elements that match any criteria. Note that elements + * are not guaranteed to be in document order. + */ +@Retention(RUNTIME) @Target({FIELD, TYPE}) +@Repeatable(WindowsFindByAllSet.class) +public @interface WindowsFindAll { + /** + * It is a set of {@link WindowsBy} strategies which may be + * used to find the target element. + */ + WindowsBy[] value(); + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; +} diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java index 8a56f9898..667814056 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java @@ -16,10 +16,12 @@ package io.appium.java_client.pagefactory; -import java.lang.annotation.ElementType; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** @@ -29,7 +31,7 @@ * this allows users to quickly and easily create PageObjects. * using Windows automation selectors, accessibility, id, name, class name, tag and xpath */ -@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RUNTIME) @Target({FIELD, TYPE}) @Repeatable(WindowsFindBySet.class) public @interface WindowsFindBy { @@ -62,4 +64,9 @@ * It is a xpath to the target element. */ String xpath() default ""; + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; } diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindByAllSet.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindByAllSet.java new file mode 100644 index 000000000..adbad0864 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindByAllSet.java @@ -0,0 +1,22 @@ +package io.appium.java_client.pagefactory; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Defines set of chained/possible locators. Each one locator + * should be defined with {@link WindowsFindAll} + */ +@Target(value = {TYPE, FIELD}) +@Retention(value = RUNTIME) +public @interface WindowsFindByAllSet { + /** + * @return an array of {@link WindowsFindAll} which builds a sequence of + * the chained searching for elements or a set of possible locators + */ + WindowsFindAll[] value(); +} diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindByChainSet.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindByChainSet.java new file mode 100644 index 000000000..b8a5bc03d --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindByChainSet.java @@ -0,0 +1,22 @@ +package io.appium.java_client.pagefactory; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Defines set of chained/possible locators. Each one locator + * should be defined with {@link WindowsFindBys} + */ +@Target(value = {TYPE, FIELD}) +@Retention(value = RUNTIME) +public @interface WindowsFindByChainSet { + /** + * @return an array of {@link WindowsFindBys} which builds a sequence of + * the chained searching for elements or a set of possible locators + */ + WindowsFindBys[] value(); +} diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java index 8512eed2b..edf7de758 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java @@ -16,17 +16,19 @@ package io.appium.java_client.pagefactory; -import java.lang.annotation.ElementType; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Defines set of chained/possible locators. Each one locator * should be defined with {@link WindowsFindBy} */ -@Target(value = {ElementType.TYPE, ElementType.FIELD}) -@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = {TYPE, FIELD}) +@Retention(value = RUNTIME) public @interface WindowsFindBySet { /** * @return an array of {@link WindowsFindBy} which builds a sequence of diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBys.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBys.java new file mode 100644 index 000000000..5bd34f460 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBys.java @@ -0,0 +1,44 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.java_client.pagefactory; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Used to mark a field on a Page Object to indicate that lookup should use + * a series of {@link WindowsBy} tags. + */ +@Retention(RUNTIME) @Target({FIELD, TYPE}) +@Repeatable(WindowsFindByChainSet.class) +public @interface WindowsFindBys { + /** + * It is a set of {@link WindowsBy} strategies which build + * the chain of the searching for the target element. + */ + WindowsBy[] value(); + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; +} diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/builder/AppiumByBuilder.java b/src/main/java/io/appium/java_client/pagefactory/bys/builder/AppiumByBuilder.java index 452f5fedc..063a4cbde 100644 --- a/src/main/java/io/appium/java_client/pagefactory/bys/builder/AppiumByBuilder.java +++ b/src/main/java/io/appium/java_client/pagefactory/bys/builder/AppiumByBuilder.java @@ -92,6 +92,10 @@ private static Method[] prepareAnnotationMethods(Class ann private static String getFilledValue(Annotation mobileBy) { Method[] values = prepareAnnotationMethods(mobileBy.getClass()); for (Method value : values) { + if (!String.class.equals(value.getReturnType())) { + continue; + } + try { String strategyParameter = value.invoke(mobileBy, new Object[] {}).toString(); if (!"".equals(strategyParameter)) { diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByChained.java b/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByChained.java index 60a9800d5..f25f1d3cc 100644 --- a/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByChained.java +++ b/src/main/java/io/appium/java_client/pagefactory/bys/builder/ByChained.java @@ -28,7 +28,7 @@ import java.util.Optional; -class ByChained extends org.openqa.selenium.support.pagefactory.ByChained { +public class ByChained extends org.openqa.selenium.support.pagefactory.ByChained { private final By[] bys; @@ -42,6 +42,9 @@ private static AppiumFunction getSearchingFunction(By }; } + /** + * @param bys is a set of {@link org.openqa.selenium.By} which forms the chain of the searching. + */ public ByChained(By[] bys) { super(bys); checkNotNull(bys); diff --git a/src/main/java/io/appium/java_client/pagefactory/bys/builder/Strategies.java b/src/main/java/io/appium/java_client/pagefactory/bys/builder/Strategies.java index 3f3961578..35fbfd6e9 100644 --- a/src/main/java/io/appium/java_client/pagefactory/bys/builder/Strategies.java +++ b/src/main/java/io/appium/java_client/pagefactory/bys/builder/Strategies.java @@ -17,7 +17,9 @@ package io.appium.java_client.pagefactory.bys.builder; import io.appium.java_client.MobileBy; +import io.appium.java_client.pagefactory.AndroidBy; import io.appium.java_client.pagefactory.AndroidFindBy; +import io.appium.java_client.pagefactory.iOSBy; import io.appium.java_client.pagefactory.iOSFindBy; import org.openqa.selenium.By; @@ -31,10 +33,12 @@ enum Strategies { BYUIAUTOMATOR("uiAutomator") { @Override By getBy(Annotation annotation) { String value = getValue(annotation, this); - if (annotation.annotationType().equals(AndroidFindBy.class)) { + if (annotation.annotationType().equals(AndroidFindBy.class) + || annotation.annotationType().equals(AndroidBy.class)) { return MobileBy.AndroidUIAutomator(value); } - if (annotation.annotationType().equals(iOSFindBy.class)) { + if (annotation.annotationType().equals(iOSFindBy.class) + || annotation.annotationType().equals(iOSBy.class)) { return MobileBy.IosUIAutomation(value); } return super.getBy(annotation); diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSBy.java b/src/main/java/io/appium/java_client/pagefactory/iOSBy.java new file mode 100644 index 000000000..0b7623bc6 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/iOSBy.java @@ -0,0 +1,62 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.java_client.pagefactory; + + +/** + * Used to build a complex iOS automation locator. + */ +public @interface iOSBy { + /** + * It is is iOS UIAutomation string. + * Read https://developer.apple.com/library/tvos/documentation/DeveloperTools/ + * Conceptual/InstrumentsUserGuide/UIAutomation.html + */ + String uiAutomator() default ""; + + /** + * It an UI automation accessibility Id which is a convenient to iOS. + * About iOS accessibility + * See UIAccessibilityIdentification + */ + String accessibility() default ""; + + /** + * It is an id of the target element. + */ + String id() default ""; + + /** + * It is a name of a type/class of the target element. + */ + String className() default ""; + + /** + * It is a desired element tag. + */ + String tagName() default ""; + + /** + * It is a xpath to the target element. + */ + String xpath() default ""; + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; +} diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSFindAll.java b/src/main/java/io/appium/java_client/pagefactory/iOSFindAll.java index a622a8246..5754583d1 100644 --- a/src/main/java/io/appium/java_client/pagefactory/iOSFindAll.java +++ b/src/main/java/io/appium/java_client/pagefactory/iOSFindAll.java @@ -16,27 +16,31 @@ package io.appium.java_client.pagefactory; -import java.lang.annotation.ElementType; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Used to mark a field on a Page/Screen Object to indicate that lookup should use a series - * of {@link io.appium.java_client.pagefactory.iOSFindBy} tags + * of {@link io.appium.java_client.pagefactory.iOSBy} tags * It will then search for all elements that match any criteria. Note that elements * are not guaranteed to be in document order. - * It is deprecated. Set of {@link io.appium.java_client.pagefactory.iOSFindBy} - * can be defined without this annotation. To define the correct way how to use - * the defined set please take a look at {@link HowToUseLocators}. The article. - * https://docs.oracle.com/javase/tutorial/java/annotations/repeating.html. */ -@Deprecated -@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RUNTIME) @Target({FIELD, TYPE}) +@Repeatable(iOSFindByAllSet.class) public @interface iOSFindAll { /** - * It is a set of {@link io.appium.java_client.pagefactory.iOSFindBy} strategies which may be + * It is a set of {@link io.appium.java_client.pagefactory.iOSBy} strategies which may be * used to find the target element. */ - iOSFindBy[] value(); + iOSBy[] value(); + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; } diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSFindBy.java b/src/main/java/io/appium/java_client/pagefactory/iOSFindBy.java index 6c193bb0a..5c365fe4a 100644 --- a/src/main/java/io/appium/java_client/pagefactory/iOSFindBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/iOSFindBy.java @@ -16,10 +16,12 @@ package io.appium.java_client.pagefactory; -import java.lang.annotation.ElementType; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -30,7 +32,7 @@ * this allows users to quickly and easily create PageObjects. * using iOS UI selectors, accessibility, id, name, class name, tag and xpath */ -@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RUNTIME) @Target({FIELD, TYPE}) @Repeatable(iOSFindBySet.class) public @interface iOSFindBy { /** @@ -66,4 +68,9 @@ * It is a xpath to the target element. */ String xpath() default ""; + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; } diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSFindByAllSet.java b/src/main/java/io/appium/java_client/pagefactory/iOSFindByAllSet.java new file mode 100644 index 000000000..cbedac322 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/iOSFindByAllSet.java @@ -0,0 +1,22 @@ +package io.appium.java_client.pagefactory; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Defines set of chained/possible locators. Each one locator + * should be defined with {@link io.appium.java_client.pagefactory.iOSFindAll} + */ +@Target(value = {TYPE, FIELD}) +@Retention(value = RUNTIME) +public @interface iOSFindByAllSet { + /** + * @return an array of {@link io.appium.java_client.pagefactory.iOSFindAll} which builds a sequence of + * the chained searching for elements or a set of possible locators + */ + iOSFindAll[] value(); +} diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSFindByChainSet.java b/src/main/java/io/appium/java_client/pagefactory/iOSFindByChainSet.java new file mode 100644 index 000000000..5b6865dd1 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/iOSFindByChainSet.java @@ -0,0 +1,22 @@ +package io.appium.java_client.pagefactory; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Defines set of chained/possible locators. Each one locator + * should be defined with {@link io.appium.java_client.pagefactory.iOSFindBys} + */ +@Target(value = {TYPE, FIELD}) +@Retention(value = RUNTIME) +public @interface iOSFindByChainSet { + /** + * @return an array of {@link io.appium.java_client.pagefactory.iOSFindBys} which builds a sequence of + * the chained searching for elements or a set of possible locators + */ + iOSFindBys[] value(); +} diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSFindBySet.java b/src/main/java/io/appium/java_client/pagefactory/iOSFindBySet.java index 28fecc160..2f876b878 100644 --- a/src/main/java/io/appium/java_client/pagefactory/iOSFindBySet.java +++ b/src/main/java/io/appium/java_client/pagefactory/iOSFindBySet.java @@ -16,17 +16,19 @@ package io.appium.java_client.pagefactory; -import java.lang.annotation.ElementType; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Defines set of chained/possible locators. Each one locator * should be defined with {@link io.appium.java_client.pagefactory.iOSFindBy} */ -@Target(value = {ElementType.TYPE, ElementType.FIELD}) -@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = {TYPE, FIELD}) +@Retention(value = RUNTIME) public @interface iOSFindBySet { /** * @return an array of {@link io.appium.java_client.pagefactory.iOSFindBy} which builds a sequence of diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSFindBys.java b/src/main/java/io/appium/java_client/pagefactory/iOSFindBys.java index 51fb3fe68..9d2f0a4c3 100644 --- a/src/main/java/io/appium/java_client/pagefactory/iOSFindBys.java +++ b/src/main/java/io/appium/java_client/pagefactory/iOSFindBys.java @@ -16,25 +16,29 @@ package io.appium.java_client.pagefactory; -import java.lang.annotation.ElementType; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Used to mark a field on a Page Object to indicate that lookup should use - * a series of {@link io.appium.java_client.pagefactory.iOSFindBy} tags. - * It is deprecated. Set of {@link io.appium.java_client.pagefactory.iOSFindBy} - * can be defined without this annotation. To define the correct way how to use - * the defined set please take a look at {@link HowToUseLocators}. The article. - * https://docs.oracle.com/javase/tutorial/java/annotations/repeating.html. + * a series of {@link io.appium.java_client.pagefactory.iOSBy} tags. */ -@Deprecated -@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RUNTIME) @Target({FIELD, TYPE}) +@Repeatable(iOSFindByChainSet.class) public @interface iOSFindBys { /** - * It is a set of {@link io.appium.java_client.pagefactory.iOSFindBy} strategies which build + * It is a set of {@link io.appium.java_client.pagefactory.iOSBy} strategies which build * the chain of the searching for the target element. */ - iOSFindBy[] value(); + iOSBy[] value(); + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; } diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITBy.java b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITBy.java new file mode 100644 index 000000000..f3a1e8100 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITBy.java @@ -0,0 +1,69 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.java_client.pagefactory; + +/** + * Used to build a complex iOS XCUIT mode locator. + */ +public @interface iOSXCUITBy { + + /** + * The Class Chain locator is similar to xpath, but it's faster and can only + * search direct children elements. See the + * + * documentation for more details. + */ + String iOSClassChain() default ""; + + /** + * The NSPredicate class is used to define logical conditions used to constrain + * a search either for a fetch or for in-memory filtering. + */ + String iOSNsPredicate() default ""; + + /** + * It an UI automation accessibility Id which is a convenient to iOS. + * About iOS accessibility + * See UIAccessibilityIdentification + */ + String accessibility() default ""; + + /** + * It is an id of the target element. + */ + String id() default ""; + + /** + * It is a name of a type/class of the target element. + */ + String className() default ""; + + /** + * It is a desired element tag. + */ + String tagName() default ""; + + /** + * It is a xpath to the target element. + */ + String xpath() default ""; + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; +} diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindAll.java b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindAll.java new file mode 100644 index 000000000..95af79c36 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindAll.java @@ -0,0 +1,46 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.java_client.pagefactory; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Used to mark a field on a Page/Screen Object to indicate that lookup should use a series + * of {@link io.appium.java_client.pagefactory.iOSXCUITBy} tags + * It will then search for all elements that match any criteria. Note that elements + * are not guaranteed to be in document order. + */ +@Retention(RUNTIME) @Target({FIELD, TYPE}) +@Repeatable(iOSXCUITFindByAllSet.class) +public @interface iOSXCUITFindAll { + /** + * It is a set of {@link io.appium.java_client.pagefactory.iOSXCUITBy} strategies which may be + * used to find the target element. + */ + iOSXCUITBy[] value(); + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; +} diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBy.java b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBy.java index 2baf2e866..63caf7cf4 100644 --- a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBy.java @@ -16,13 +16,15 @@ package io.appium.java_client.pagefactory; -import java.lang.annotation.ElementType; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RUNTIME) @Target({FIELD, TYPE}) @Repeatable(iOSXCUITFindBySet.class) public @interface iOSXCUITFindBy { @@ -66,4 +68,9 @@ * It is a xpath to the target element. */ String xpath() default ""; + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; } diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindByAllSet.java b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindByAllSet.java new file mode 100644 index 000000000..a0adcf8f2 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindByAllSet.java @@ -0,0 +1,22 @@ +package io.appium.java_client.pagefactory; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Defines set of chained/possible locators. Each one locator + * should be defined with {@link io.appium.java_client.pagefactory.iOSXCUITFindAll} + */ +@Target(value = {TYPE, FIELD}) +@Retention(value = RUNTIME) +public @interface iOSXCUITFindByAllSet { + /** + * @return an array of {@link io.appium.java_client.pagefactory.iOSXCUITFindAll} which builds a sequence of + * the chained searching for elements or a set of possible locators + */ + iOSXCUITFindAll[] value(); +} diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindByChainSet.java b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindByChainSet.java new file mode 100644 index 000000000..056931c8a --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindByChainSet.java @@ -0,0 +1,22 @@ +package io.appium.java_client.pagefactory; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Defines set of chained/possible locators. Each one locator + * should be defined with {@link io.appium.java_client.pagefactory.iOSXCUITFindBys} + */ +@Target(value = {TYPE, FIELD}) +@Retention(value = RUNTIME) +public @interface iOSXCUITFindByChainSet { + /** + * @return an array of {@link io.appium.java_client.pagefactory.iOSXCUITFindBys} which builds a sequence of + * the chained searching for elements or a set of possible locators + */ + iOSXCUITFindBys[] value(); +} diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBySet.java b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBySet.java index f2f43e7b8..2d3b0c02b 100644 --- a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBySet.java +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBySet.java @@ -16,12 +16,14 @@ package io.appium.java_client.pagefactory; -import java.lang.annotation.ElementType; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RUNTIME) @Target({FIELD, TYPE}) public @interface iOSXCUITFindBySet { /** * @return an array of {@link iOSXCUITFindBy} which builds a sequence of diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBys.java b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBys.java new file mode 100644 index 000000000..ad241f445 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBys.java @@ -0,0 +1,44 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.java_client.pagefactory; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Used to mark a field on a Page Object to indicate that lookup should use + * a series of {@link io.appium.java_client.pagefactory.iOSXCUITBy} tags. + */ +@Retention(RUNTIME) @Target({FIELD, TYPE}) +@Repeatable(iOSXCUITFindByChainSet.class) +public @interface iOSXCUITFindBys { + /** + * It is a set of {@link io.appium.java_client.pagefactory.iOSXCUITBy} strategies which build + * the chain of the searching for the target element. + */ + iOSXCUITBy[] value(); + + /** + * @return priority of the searching. Higher number means lower priority. + */ + int priority() default 0; +} diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java index b725c1590..550b58b6f 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java @@ -25,12 +25,15 @@ import io.appium.java_client.android.AndroidElement; import io.appium.java_client.android.BaseAndroidTest; + +import io.appium.java_client.pagefactory.AndroidBy; +import io.appium.java_client.pagefactory.AndroidFindAll; import io.appium.java_client.pagefactory.AndroidFindBy; +import io.appium.java_client.pagefactory.AndroidFindBys; import io.appium.java_client.pagefactory.AppiumFieldDecorator; import io.appium.java_client.pagefactory.HowToUseLocators; import io.appium.java_client.pagefactory.SelendroidFindBy; import io.appium.java_client.pagefactory.iOSFindBy; - import org.junit.Before; import org.junit.Test; import org.openqa.selenium.NoSuchElementException; @@ -180,6 +183,59 @@ public class AndroidPageObjectTest extends BaseAndroidTest { @AndroidFindBy(id = "android:id/text1") private List elementsFoundByInvalidChainedSelector; + @AndroidFindBy(id = "android:id/text1", priority = 2) + @AndroidFindAll(value = { + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")"), + @AndroidBy(id = "android:id/fakeId")}, priority = 1) + @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")") + private AndroidElement androidElementViewFoundByMixedSearching; + + @AndroidFindBy(id = "android:id/text1", priority = 2) + @AndroidFindAll(value = { + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")"), + @AndroidBy(id = "android:id/fakeId")}, priority = 1) + @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")") + private List androidElementsViewFoundByMixedSearching; + + @AndroidFindBys({ + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")", priority = 1), + @AndroidBy(className = "android.widget.FrameLayout")}) + @AndroidFindBys({@AndroidBy(id = "android:id/text1", priority = 1), + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")")}) + private AndroidElement androidElementViewFoundByMixedSearching2; + + @AndroidFindBys({ + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")", priority = 1), + @AndroidBy(className = "android.widget.FrameLayout")}) + @AndroidFindBys({ + @AndroidBy(id = "android:id/text1", priority = 1), + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")")}) + private List androidElementsViewFoundByMixedSearching2; + + @HowToUseLocators(androidAutomation = ALL_POSSIBLE) + @AndroidFindBy(id = "android:id/fakeId1") + @AndroidFindBy(id = "android:id/fakeId2", priority = 1) + @AndroidFindBys(value = { + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")", priority = 1), + @AndroidBy(id = "android:id/text1", priority = 3), + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")", priority = 2), + @AndroidBy(className = "android.widget.FrameLayout")}, priority = 2) + @AndroidFindBy(id = "android:id/fakeId3", priority = 3) + @AndroidFindBy(id = "android:id/fakeId4", priority = 4) + private AndroidElement androidElementViewFoundByMixedSearching3; + + @HowToUseLocators(androidAutomation = ALL_POSSIBLE) + @AndroidFindBy(id = "android:id/fakeId1") + @AndroidFindBy(id = "android:id/fakeId2", priority = 1) + @AndroidFindBys(value = { + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")", priority = 1), + @AndroidBy(id = "android:id/text1", priority = 3), + @AndroidBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")", priority = 2), + @AndroidBy(className = "android.widget.FrameLayout")}, priority = 2) + @AndroidFindBy(id = "android:id/fakeId3", priority = 3) + @AndroidFindBy(id = "android:id/fakeId4", priority = 4) + private List androidElementsViewFoundByMixedSearching3; + /** * The setting up. */ @@ -333,4 +389,28 @@ public void checkThatElementSearchingThrowsExpectedExceptionIfChainedLocatorIsIn @Test public void checkThatListSearchingWorksIfChainedLocatorIsInvalid() { assertEquals(0, elementsFoundByInvalidChainedSelector.size()); } + + @Test public void checkMixedElementSearching1() { + assertNotNull(androidElementViewFoundByMixedSearching.getAttribute("text")); + } + + @Test public void checkMixedElementsSearching1() { + assertNotEquals(0, androidElementsViewFoundByMixedSearching.size()); + } + + @Test public void checkMixedElementSearching2() { + assertNotNull(androidElementViewFoundByMixedSearching2.getAttribute("text")); + } + + @Test public void checkMixedElementsSearching2() { + assertNotEquals(0, androidElementsViewFoundByMixedSearching2.size()); + } + + @Test public void checkMixedElementSearching3() { + assertNotNull(androidElementViewFoundByMixedSearching3.getAttribute("text")); + } + + @Test public void checkMixedElementsSearching3() { + assertNotEquals(0, androidElementsViewFoundByMixedSearching3.size()); + } } diff --git a/src/test/java/io/appium/java_client/utils/ScreenshotStateTests.java b/src/test/java/io/appium/java_client/utils/ScreenshotStateTests.java index 1868db56b..1f730a60e 100644 --- a/src/test/java/io/appium/java_client/utils/ScreenshotStateTests.java +++ b/src/test/java/io/appium/java_client/utils/ScreenshotStateTests.java @@ -1,5 +1,11 @@ package io.appium.java_client.utils; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.lessThan; +import static org.junit.Assert.assertThat; + import io.appium.java_client.ScreenshotState; import org.junit.Before; import org.junit.Test; @@ -11,12 +17,6 @@ import java.time.Duration; import java.util.Random; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.Matchers.lessThan; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; -import static org.junit.Assert.assertThat; - public class ScreenshotStateTests { private static final Random rand = new Random(); private static final Duration ONE_SECOND = Duration.ofSeconds(1);