diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumAnnotations.java b/src/main/java/io/appium/java_client/pagefactory/AppiumAnnotations.java index 41b9e4218..afd3d118c 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumAnnotations.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumAnnotations.java @@ -137,12 +137,15 @@ By getBy(Annotation annotation) { private final Field mobileField; private final String platform; + private final String automation; - AppiumAnnotations(Field field, String platform) { + AppiumAnnotations(Field field, String platform, String automation) { super(field); mobileField = field; this.platform = String.valueOf(platform). toUpperCase().trim(); + this.automation = String.valueOf(automation). + toUpperCase().trim(); } private static void checkDisallowedAnnotationPairs(Annotation a1, @@ -161,7 +164,14 @@ private void assertValidAnnotations() { .getAnnotation(AndroidFindBys.class); AndroidFindAll androidFindAll = mobileField. getAnnotation(AndroidFindAll.class); - + + SelendroidFindBy selendroidBy = mobileField + .getAnnotation(SelendroidFindBy.class); + SelendroidFindBys selendroidBys = mobileField + .getAnnotation(SelendroidFindBys.class); + SelendroidFindAll selendroidFindAll = mobileField. + getAnnotation(SelendroidFindAll.class); + iOSFindBy iOSBy = mobileField.getAnnotation(iOSFindBy.class); iOSFindBys iOSBys = mobileField.getAnnotation(iOSFindBys.class); iOSFindAll iOSFindAll = mobileField.getAnnotation(iOSFindAll.class); @@ -169,6 +179,10 @@ private void assertValidAnnotations() { checkDisallowedAnnotationPairs(androidBy, androidBys); checkDisallowedAnnotationPairs(androidBy, androidFindAll); checkDisallowedAnnotationPairs(androidBys, androidFindAll); + + checkDisallowedAnnotationPairs(selendroidBy, selendroidBys); + checkDisallowedAnnotationPairs(selendroidBy, selendroidFindAll); + checkDisallowedAnnotationPairs(selendroidBys, selendroidFindAll); checkDisallowedAnnotationPairs(iOSBy, iOSBys); checkDisallowedAnnotationPairs(iOSBy, iOSFindAll); @@ -248,12 +262,34 @@ private T getComplexMobileBy(Annotation[] annotations, Class r public By buildBy() { assertValidAnnotations(); + SelendroidFindBy selendroidBy = mobileField + .getAnnotation(SelendroidFindBy.class); + if (selendroidBy != null && ANDROID.toUpperCase().equals(platform) && + "Selendroid".toUpperCase().equals(automation)) { + return getMobileBy(selendroidBy, getFilledValue(selendroidBy)); + } + + SelendroidFindBys selendroidBys = mobileField + .getAnnotation(SelendroidFindBys.class); + if (selendroidBys != null && ANDROID.toUpperCase().equals(platform) && + "Selendroid".toUpperCase().equals(automation)) { + return getComplexMobileBy(selendroidBys.value(), ByChained.class); + } + + SelendroidFindAll selendroidAll = mobileField + .getAnnotation(SelendroidFindAll.class); + if (selendroidAll != null && ANDROID.toUpperCase().equals(platform) && + "Selendroid".toUpperCase().equals(automation)) { + return getComplexMobileBy(selendroidAll.value(), ByAll.class); + } + + AndroidFindBy androidBy = mobileField .getAnnotation(AndroidFindBy.class); if (androidBy != null && ANDROID.toUpperCase().equals(platform)) { return getMobileBy(androidBy, getFilledValue(androidBy)); } - + AndroidFindBys androidBys = mobileField .getAnnotation(AndroidFindBys.class); if (androidBys != null && ANDROID.toUpperCase().equals(platform)) { @@ -265,6 +301,7 @@ public By buildBy() { return getComplexMobileBy(androidFindAll.value(), ByAll.class); } + iOSFindBy iOSBy = mobileField.getAnnotation(iOSFindBy.class); if (iOSBy != null && IOS.toUpperCase().equals(platform)) { return getMobileBy(iOSBy, getFilledValue(iOSBy)); diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java index d35b6628b..1095a11fa 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java @@ -71,7 +71,12 @@ public List apply(By by) { .valueOf(((HasCapabilities) unpackWebDriverFromSearchContext()) .getCapabilities().getCapability( MobileCapabilityType.PLATFORM_NAME)); - AppiumAnnotations annotations = new AppiumAnnotations(field, platform); + String automation = String + .valueOf(((HasCapabilities) unpackWebDriverFromSearchContext()) + .getCapabilities().getCapability( + MobileCapabilityType.AUTOMATION_NAME)); + + AppiumAnnotations annotations = new AppiumAnnotations(field, platform, automation); this.timeOutContainer = timeOutContainer; shouldCache = annotations.isLookupCached(); by = annotations.buildBy(); diff --git a/src/main/java/io/appium/java_client/pagefactory/SelendroidFindAll.java b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindAll.java new file mode 100644 index 000000000..c66b80231 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindAll.java @@ -0,0 +1,17 @@ +package io.appium.java_client.pagefactory; + +import java.lang.annotation.ElementType; +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 SelendroidFindBy} tags + * It will then search for all elements that match any criteria. Note that elements + * are not guaranteed to be in document order. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.TYPE}) +public @interface SelendroidFindAll { + SelendroidFindBy[] value(); +} diff --git a/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBy.java b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBy.java new file mode 100644 index 000000000..49ba5915f --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBy.java @@ -0,0 +1,24 @@ +package io.appium.java_client.pagefactory; + +import java.lang.annotation.ElementType; +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 an alternative mechanism for locating the + * element or a list of elements. Used in conjunction with + * {@link org.openqa.selenium.support.PageFactory} + * 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}) +public @interface SelendroidFindBy { + String id() default ""; + String name() default ""; + String className() default ""; + String tagName() default ""; + String xpath() default ""; +} diff --git a/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBys.java b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBys.java new file mode 100644 index 000000000..725a48dbc --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/SelendroidFindBys.java @@ -0,0 +1,15 @@ +package io.appium.java_client.pagefactory; + +import java.lang.annotation.ElementType; +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 @SelendroidFindBy tags + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.TYPE}) +public @interface SelendroidFindBys { + SelendroidFindBy[] value(); +} 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 1ca54b77d..e5a8fc9ef 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 @@ -7,6 +7,7 @@ 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.SelendroidFindBy; import io.appium.java_client.pagefactory.iOSFindBy; import io.appium.java_client.pagefactory.iOSFindBys; import io.appium.java_client.remote.MobileCapabilityType; @@ -148,6 +149,10 @@ public class AndroidPageObjectTest { @AndroidFindBy(id = "android:id/FakeId") }) private WebElement findAllElementView; + + @AndroidFindBy(id = "android:id/text1") + @SelendroidFindBy(id = "Invalid Identifier") + private WebElement textAndroidId; @Before public void setUp() throws Exception { @@ -311,4 +316,9 @@ public void findAllElementTest(){ public void findAllElementsTest(){ Assert.assertNotEquals(0, findAllElementViews.size()); } + + @Test + public void findByAndroidAnnotationOnlyTest(){ + Assert.assertNotEquals(null, textAndroidId.getAttribute("text")); + } } diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/SelendroidModeTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/SelendroidModeTest.java new file mode 100644 index 000000000..3907d71f3 --- /dev/null +++ b/src/test/java/io/appium/java_client/pagefactory_tests/SelendroidModeTest.java @@ -0,0 +1,143 @@ +package io.appium.java_client.pagefactory_tests; + +import io.appium.java_client.android.AndroidDriver; +import io.appium.java_client.pagefactory.AndroidFindBy; +import io.appium.java_client.pagefactory.AppiumFieldDecorator; +import io.appium.java_client.pagefactory.SelendroidFindAll; +import io.appium.java_client.pagefactory.SelendroidFindBy; +import io.appium.java_client.pagefactory.SelendroidFindBys; +import io.appium.java_client.remote.MobileCapabilityType; +import org.openqa.selenium.WebElement; +import java.io.File; +import java.net.URL; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.remote.DesiredCapabilities; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.PageFactory; + +public class SelendroidModeTest { + + private WebDriver driver; + + @SelendroidFindBy(id = "text1") + private WebElement textId; + + @AndroidFindBy(id = "Invalid Identifier") + @SelendroidFindBy(id = "text1") + private WebElement textSelendroidId; + + @SelendroidFindBy(name = "Accessibility") + private WebElement textName; + + @AndroidFindBy(name = "Accessibility") + private WebElement textNameAndroid; + + @FindBy(name = "Accessibility") + private WebElement textNameDefault; + + @SelendroidFindBy(xpath = "//TextView[@value='Accessibility']") + private WebElement textXpath; + + @SelendroidFindBys({ + @SelendroidFindBy(id = "text1")}) + private WebElement textIds; + + @SelendroidFindAll({ + @SelendroidFindBy(id = "text1")}) + private WebElement textAll; + + @SelendroidFindAll({ + @SelendroidFindBy(id = "text1")}) + private List textsAll; + + @SelendroidFindBy(className = "android.widget.TextView") + private WebElement textClass; + + @SelendroidFindBy(tagName = "TextView") + private WebElement textTag; + + @Before + public void setUp() throws Exception { + File appDir = new File("src/test/java/io/appium/java_client"); + File app = new File(appDir, "ApiDemos-debug.apk"); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); + capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, "Selendroid"); + driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); + + //This time out is set because test can be run on slow Android SDK emulator + PageFactory.initElements(new AppiumFieldDecorator(driver, 5, TimeUnit.SECONDS), this); + } + + @After + public void tearDown() throws Exception { + driver.quit(); + } + + @Test + public void findByIdElementTest() { + Assert.assertNotEquals(null, textId.getAttribute("text")); + } + + @Test + public void findBySelendroidSelectorTest() { + Assert.assertNotEquals(null, textSelendroidId.getAttribute("text")); + } + + @Test + public void findByElementByNameTest() { + Assert.assertEquals("Accessibility", textName.getText()); + } + + @Test + public void findByElementByNameAndroidTest() { + Assert.assertEquals("Accessibility", textNameAndroid.getText()); + } + + @Test + public void findByElementByNameDefaultTest() { + Assert.assertEquals("Accessibility", textNameDefault.getText()); + } + + @Test + public void findByElementByXpathTest() { + Assert.assertEquals("Accessibility", textXpath.getText()); + } + + @Test + public void findByElementByIdsTest() { + Assert.assertNotNull(textIds.getText()); + } + + @Test + public void findByElementByTestAllTest() { + Assert.assertNotNull(textAll.getText()); + } + + @Test + public void findByElementByTextsAllTest() { + Assert.assertTrue(textsAll.size() > 1); + } + + @Test + public void findByElementByCalssTest() { + Assert.assertNotEquals(null, textClass.getAttribute("text")); + } + + @Test + public void findByElementByTagTest() { + Assert.assertNotEquals(null, textTag.getAttribute("text")); + } + @Test + public void findBySelendroidAnnotationOnlyTest() { + Assert.assertNotEquals(null, textSelendroidId.getAttribute("text")); + } + +}