diff --git a/README.md b/README.md
index a67b56fd3..9adc60380 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ Depends upon the Selenium Java client library, available [here](http://docs.sele
io.appium
java-client
- 1.3.0
+ 1.5.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
new file mode 100644
index 000000000..29f6da8de
--- /dev/null
+++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java
@@ -0,0 +1,26 @@
+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 AndroidFindBy {
+ String uiAutomator() default "";
+ String accessibility() default "";
+ 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/AndroidFindBys.java b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBys.java
new file mode 100644
index 000000000..ddcb77633
--- /dev/null
+++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBys.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 @AndroidFindBy tags
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.TYPE})
+public @interface AndroidFindBys {
+ AndroidFindBy[] value();
+}
diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumAnnotations.java b/src/main/java/io/appium/java_client/pagefactory/AppiumAnnotations.java
new file mode 100644
index 000000000..6c8d48b67
--- /dev/null
+++ b/src/main/java/io/appium/java_client/pagefactory/AppiumAnnotations.java
@@ -0,0 +1,261 @@
+package io.appium.java_client.pagefactory;
+
+import io.appium.java_client.MobileBy;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.support.pagefactory.Annotations;
+import org.openqa.selenium.support.pagefactory.ByChained;
+
+class AppiumAnnotations extends Annotations{
+
+ private final static List METHODS_TO_BE_EXCLUDED_WHEN_ANNOTATION_IS_READ = new ArrayList() {
+ private static final long serialVersionUID = 1L;
+ {
+ List objectClassMethodNames = getMethodNames(Object.class
+ .getDeclaredMethods());
+ addAll(objectClassMethodNames);
+ List annotationClassMethodNames = getMethodNames(Annotation.class
+ .getDeclaredMethods());
+ annotationClassMethodNames.removeAll(objectClassMethodNames);
+ addAll(annotationClassMethodNames);
+ }
+ };
+ private final static Class>[] DEFAULT_ANNOTATION_METHOD_ARGUMENTS = new Class>[] {};
+ private final static String ANDROID_PLATFORM = "Android";
+ private final static String IOS_PLATFORM = "iOS";
+
+ private static List getMethodNames(Method[] methods) {
+ List names = new ArrayList();
+ for (Method m : methods) {
+ names.add(m.getName());
+ }
+ return names;
+ }
+
+ private static enum Strategies {
+ BYUIAUTOMATOR("uiAutomator") {
+ @Override
+ By getBy(Annotation annotation) {
+ String value = getValue(annotation, this);
+ if (annotation.annotationType().equals(AndroidFindBy.class)) {
+ return MobileBy.AndroidUIAutomator(value);
+ }
+ if (annotation.annotationType().equals(iOSFindBy.class)) {
+ return MobileBy.IosUIAutomation(value);
+ }
+ return super.getBy(annotation);
+ }
+ },
+ BYACCESSABILITY("accessibility") {
+ @Override
+ By getBy(Annotation annotation) {
+ return MobileBy.AccessibilityId(getValue(annotation, this));
+ }
+ },
+ BYCLASSNAME("className") {
+ @Override
+ By getBy(Annotation annotation) {
+ return By.className(getValue(annotation, this));
+ }
+ },
+ BYID("id") {
+ @Override
+ By getBy(Annotation annotation) {
+ return By.id(getValue(annotation, this));
+ }
+ },
+ BYTAG("tagName") {
+ @Override
+ By getBy(Annotation annotation) {
+ return By.tagName(getValue(annotation, this));
+ }
+ },
+ BYNAME("name") {
+ @Override
+ By getBy(Annotation annotation) {
+ return By.name(getValue(annotation, this));
+ }
+ },
+ BYXPATH("xpath") {
+ @Override
+ By getBy(Annotation annotation) {
+ return By.xpath(getValue(annotation, this));
+ }
+ };
+
+ private final String valueName;
+
+ private String returnValueName() {
+ return valueName;
+ }
+
+ private Strategies(String valueName) {
+ this.valueName = valueName;
+ }
+
+ private static String[] strategiesNames() {
+ Strategies[] strategies = values();
+ String[] result = new String[strategies.length];
+ int i = 0;
+ for (Strategies strategy : values()) {
+ result[i] = strategy.valueName;
+ i++;
+ }
+ return result;
+ }
+
+ private static String getValue(Annotation annotation,
+ Strategies strategy) {
+ try {
+ Method m = annotation.getClass().getMethod(strategy.valueName,
+ DEFAULT_ANNOTATION_METHOD_ARGUMENTS);
+ return m.invoke(annotation, new Object[] {}).toString();
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ } catch (SecurityException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ By getBy(Annotation annotation) {
+ return null;
+ }
+ }
+
+ private final Field mobileField;
+ private final String platform;
+
+ AppiumAnnotations(Field field, String platform) {
+ super(field);
+ mobileField = field;
+ this.platform = String.valueOf(platform).
+ toUpperCase().trim();
+ }
+
+ private void assertValidAnnotations() {
+ AndroidFindBy androidBy = mobileField
+ .getAnnotation(AndroidFindBy.class);
+ AndroidFindBys androidBys = mobileField
+ .getAnnotation(AndroidFindBys.class);
+
+ iOSFindBy iOSBy = mobileField.getAnnotation(iOSFindBy.class);
+ iOSFindBys iOSBys = mobileField.getAnnotation(iOSFindBys.class);
+
+ if (androidBy != null && androidBys != null) {
+ throw new IllegalArgumentException(
+ "If you use a '@AndroidFindBy' annotation, "
+ + "you must not also use a '@AndroidFindBys' annotation");
+ }
+
+ if (iOSBy != null && iOSBys != null) {
+ throw new IllegalArgumentException(
+ "If you use a '@iOSFindBy' annotation, "
+ + "you must not also use a '@iOSFindBys' annotation");
+ }
+ }
+
+ private static Method[] prepareAnnotationMethods(
+ Class extends Annotation> annotation) {
+ List targeAnnotationMethodNamesList = getMethodNames(annotation.getDeclaredMethods());
+ targeAnnotationMethodNamesList.removeAll(METHODS_TO_BE_EXCLUDED_WHEN_ANNOTATION_IS_READ);
+ Method[] result = new Method[targeAnnotationMethodNamesList.size()];
+ for (String methodName: targeAnnotationMethodNamesList){
+ try {
+ result[targeAnnotationMethodNamesList.indexOf(methodName)] = annotation.getMethod(methodName, DEFAULT_ANNOTATION_METHOD_ARGUMENTS);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ } catch (SecurityException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return result;
+ }
+
+ // I suppose that only @AndroidFindBy and @iOSFindBy will be here
+ private static String getFilledValue(Annotation mobileBy) {
+ Method[] values = prepareAnnotationMethods(mobileBy.getClass());
+ for (Method value : values) {
+ try {
+ String strategyParameter = value.invoke(mobileBy,
+ new Object[] {}).toString();
+ if (!"".equals(strategyParameter)) {
+ return value.getName();
+ }
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ throw new IllegalArgumentException("@"
+ + mobileBy.getClass().getSimpleName() + ": one of "
+ + Strategies.strategiesNames().toString() + " should be filled");
+ }
+
+ private By getMobileBy(Annotation annotation, String valueName) {
+ Strategies strategies[] = Strategies.values();
+ for (Strategies strategy : strategies) {
+ if (strategy.returnValueName().equals(valueName)) {
+ return strategy.getBy(annotation);
+ }
+ }
+ throw new IllegalArgumentException("@"
+ + annotation.getClass().getSimpleName()
+ + ": There is an unknown strategy " + valueName);
+ }
+
+ private By getChainedMobileBy(Annotation[] annotations) {
+ ;
+ By[] byArray = new By[annotations.length];
+ for (int i = 0; i < annotations.length; i++) {
+ byArray[i] = getMobileBy(annotations[i],
+ getFilledValue(annotations[i]));
+ }
+ return new ByChained(byArray);
+ }
+
+ @Override
+ public By buildBy() {
+ assertValidAnnotations();
+
+ AndroidFindBy androidBy = mobileField
+ .getAnnotation(AndroidFindBy.class);
+ if (androidBy != null && ANDROID_PLATFORM.toUpperCase().equals(platform)) {
+ return getMobileBy(androidBy, getFilledValue(androidBy));
+ }
+
+ AndroidFindBys androidBys = mobileField
+ .getAnnotation(AndroidFindBys.class);
+ if (androidBys != null && ANDROID_PLATFORM.toUpperCase().equals(platform)) {
+ return getChainedMobileBy(androidBys.value());
+ }
+
+ iOSFindBy iOSBy = mobileField.getAnnotation(iOSFindBy.class);
+ if (iOSBy != null && IOS_PLATFORM.toUpperCase().equals(platform)) {
+ return getMobileBy(iOSBy, getFilledValue(iOSBy));
+ }
+
+ iOSFindBys iOSBys = mobileField.getAnnotation(iOSFindBys.class);
+ if (iOSBys != null && IOS_PLATFORM.toUpperCase().equals(platform)) {
+ return getChainedMobileBy(iOSBys.value());
+ }
+
+ return super.buildBy();
+ }
+
+}
diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java
new file mode 100644
index 000000000..7216d1916
--- /dev/null
+++ b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java
@@ -0,0 +1,154 @@
+package io.appium.java_client.pagefactory;
+
+import io.appium.java_client.remote.MobileCapabilityType;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.HasCapabilities;
+import org.openqa.selenium.NoSuchElementException;
+import org.openqa.selenium.SearchContext;
+import org.openqa.selenium.StaleElementReferenceException;
+import org.openqa.selenium.TimeoutException;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.internal.WrapsDriver;
+import org.openqa.selenium.internal.WrapsElement;
+import org.openqa.selenium.support.pagefactory.ElementLocator;
+import org.openqa.selenium.support.ui.FluentWait;
+
+import com.google.common.base.Function;
+
+class AppiumElementLocator implements ElementLocator {
+
+ // This function waits for not empty element list using all defined by
+ private static class WaitingFunction implements
+ Function> {
+ private final SearchContext searchContext;
+
+ private WaitingFunction(SearchContext searchContext) {
+ this.searchContext = searchContext;
+ }
+
+ public List apply(By by) {
+ List result = new ArrayList();
+ try {
+ result.addAll(searchContext.findElements(by));
+ } catch (StaleElementReferenceException ignored) {}
+ if (result.size() > 0) {
+ return result;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ private final SearchContext searchContext;
+ private final boolean shouldCache;
+ private final By by;
+ private WebElement cachedElement;
+ private List cachedElementList;
+
+ private final long implicitlyWaitTimeOut;
+ private final TimeUnit timeUnit ;
+
+ /**
+ * Creates a new mobile element locator. It instantiates {@link WebElement}
+ * using @AndroidFindBy (-s), @iOSFindBy (-s) and @FindBy (-s) annotation sets
+ *
+ * @param searchContext
+ * The context to use when finding the element
+ * @param field
+ * The field on the Page Object that will hold the located value
+ */
+ public AppiumElementLocator(SearchContext searchContext, Field field,
+ long implicitlyWaitTimeOut, TimeUnit timeUnit) {
+ this.searchContext = searchContext;
+ // All known webdrivers implement HasCapabilities
+ String platform = String
+ .valueOf(((HasCapabilities) unpackWebDriverFromSearchContext())
+ .getCapabilities().getCapability(
+ MobileCapabilityType.PLATFORM_NAME));
+ AppiumAnnotations annotations = new AppiumAnnotations(field, platform);
+ this.implicitlyWaitTimeOut = implicitlyWaitTimeOut;
+ this.timeUnit = timeUnit;
+ shouldCache = annotations.isLookupCached();
+ by = annotations.buildBy();
+ }
+
+ private WebDriver unpackWebDriverFromSearchContext(){
+ WebDriver driver = null;
+ if (searchContext instanceof WebDriver){
+ driver = (WebDriver) searchContext;
+ }
+ //Search context it is not only Webdriver. Webelement is search context too.
+ //RemoteWebElement and MobileElement implement WrapsDriver
+ if (searchContext instanceof WebElement){
+ WebElement element = (WebElement) searchContext; //there can be something that
+ //implements WebElement interface and wraps original
+ while (element instanceof WrapsElement){
+ element = ((WrapsElement) element).getWrappedElement();
+ }
+ driver = ((WrapsDriver) searchContext).getWrappedDriver();
+ }
+ return driver;
+ }
+
+ private void changeImplicitlyWaitTimeOut(long newTimeOut, TimeUnit newTimeUnit){
+ unpackWebDriverFromSearchContext().manage().timeouts().implicitlyWait(newTimeOut, newTimeUnit);
+ }
+
+ //This method waits for not empty element list using all defined by
+ private List waitFor(){
+ //When we use complex By strategies (like ChainedBy or ByAll)
+ //there are some problems (StaleElementReferenceException, implicitly wait time out
+ //for each chain By section, etc)
+ try{
+ changeImplicitlyWaitTimeOut(0, TimeUnit.SECONDS);
+ FluentWait wait = new FluentWait(by);
+ wait.withTimeout(implicitlyWaitTimeOut, timeUnit);
+ return wait.until(new WaitingFunction(searchContext));
+ }
+ catch (TimeoutException e){
+ return new ArrayList();
+ }
+ finally{
+ changeImplicitlyWaitTimeOut(implicitlyWaitTimeOut, timeUnit);
+ }
+ }
+
+ /**
+ * Find the element.
+ */
+ public WebElement findElement() {
+ if (cachedElement != null && shouldCache) {
+ return cachedElement;
+ }
+ List result = waitFor();
+ if (result.size() == 0){
+ String message = "Cann't locate an element by this strategy: " + by.toString();
+ throw new NoSuchElementException(message);
+ }
+ if (shouldCache) {
+ cachedElement = result.get(0);
+ }
+ return result.get(0);
+ }
+
+ /**
+ * Find the element list.
+ */
+ public List findElements() {
+ if (cachedElementList != null && shouldCache) {
+ return cachedElementList;
+ }
+ List result = waitFor();
+ if (shouldCache) {
+ cachedElementList = result;
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java
new file mode 100644
index 000000000..8bcbeb62f
--- /dev/null
+++ b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java
@@ -0,0 +1,38 @@
+package io.appium.java_client.pagefactory;
+
+import java.lang.reflect.Field;
+import java.util.concurrent.TimeUnit;
+
+import org.openqa.selenium.SearchContext;
+import org.openqa.selenium.support.pagefactory.ElementLocator;
+import org.openqa.selenium.support.pagefactory.ElementLocatorFactory;
+
+class AppiumElementLocatorFactory implements ElementLocatorFactory, ResetsImplicitlyWaitTimeOut {
+ private static long DEFAULT_IMPLICITLY_WAIT_TIMEOUT = 1;
+ private static TimeUnit DEFAULT_TIMEUNIT = TimeUnit.SECONDS;
+
+ private final SearchContext searchContext;
+ private long implicitlyWaitTimeOut;
+ private TimeUnit timeUnit;
+
+ public AppiumElementLocatorFactory(SearchContext searchContext,
+ long implicitlyWaitTimeOut, TimeUnit timeUnit) {
+ this.searchContext = searchContext;
+ this.implicitlyWaitTimeOut = implicitlyWaitTimeOut;
+ this.timeUnit = timeUnit;
+ }
+
+ public AppiumElementLocatorFactory(SearchContext searchContext) {
+ this(searchContext, DEFAULT_IMPLICITLY_WAIT_TIMEOUT, DEFAULT_TIMEUNIT);
+ }
+
+ public ElementLocator createLocator(Field field) {
+ return new AppiumElementLocator(searchContext, field, implicitlyWaitTimeOut, timeUnit);
+ }
+
+ @Override
+ public void resetImplicitlyWaitTimeOut(long timeOut, TimeUnit timeUnit) {
+ implicitlyWaitTimeOut = timeOut;
+ this.timeUnit = timeUnit;
+ }
+}
diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java
new file mode 100644
index 000000000..e37ddb5ed
--- /dev/null
+++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java
@@ -0,0 +1,112 @@
+package io.appium.java_client.pagefactory;
+
+import io.appium.java_client.MobileElement;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.openqa.selenium.SearchContext;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.remote.RemoteWebElement;
+import org.openqa.selenium.support.FindAll;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.FindBys;
+import org.openqa.selenium.support.pagefactory.ElementLocator;
+import org.openqa.selenium.support.pagefactory.FieldDecorator;
+
+/**
+ * Default decorator for use with PageFactory. Will decorate 1) all of the
+ * WebElement fields and 2) List fields that have
+ * {@literal @AndroidFindBy}, {@literal @AndroidFindBys}, or
+ * {@literal @iOSFindBy/@iOSFindBys} annotation with a proxy that locates the
+ * elements using the passed in ElementLocatorFactory.
+ *
+ * Please pay attention: fields of {@link WebElement}, {@link RemoteWebElement} and
+ * {@link MobileElement} are allowed to use with this decorator
+ */
+public class AppiumFieldDecorator implements FieldDecorator, ResetsImplicitlyWaitTimeOut {
+ private final AppiumElementLocatorFactory factory;
+
+ public AppiumFieldDecorator(SearchContext context, long implicitlyWaitTimeOut, TimeUnit timeUnit) {
+ factory = new AppiumElementLocatorFactory(context, implicitlyWaitTimeOut, timeUnit);
+ }
+
+ public AppiumFieldDecorator(SearchContext context) {
+ factory = new AppiumElementLocatorFactory(context);
+ }
+
+ public Object decorate(ClassLoader ignored, Field field) {
+ if (!(WebElement.class.isAssignableFrom(field.getType()) || isDecoratableList(field))) {
+ return null;
+ }
+
+ ElementLocator locator = factory.createLocator(field);
+ if (locator == null) {
+ return null;
+ }
+
+ if (WebElement.class.isAssignableFrom(field.getType())) {
+ return proxyForLocator(field, locator);
+ } else if (List.class.isAssignableFrom(field.getType())) {
+ return proxyForListLocator(locator);
+ } else {
+ return null;
+ }
+ }
+
+ private boolean isDecoratableList(Field field) {
+ if (!List.class.isAssignableFrom(field.getType())) {
+ return false;
+ }
+
+ // Type erasure in Java isn't complete. Attempt to discover the generic
+ // type of the list.
+ Type genericType = field.getGenericType();
+ if (!(genericType instanceof ParameterizedType)) {
+ return false;
+ }
+
+ Type listType = ((ParameterizedType) genericType).getActualTypeArguments()[0];
+
+ if (!WebElement.class.equals(listType) && RemoteWebElement.class.equals(listType)
+ && MobileElement.class.equals(listType)) {
+ return false;
+ }
+
+
+ if (field.getAnnotation(AndroidFindBy.class) == null
+ && field.getAnnotation(iOSFindBy.class) == null
+ && field.getAnnotation(AndroidFindBys.class) == null
+ && field.getAnnotation(iOSFindBys.class) == null
+ && field.getAnnotation(FindBy.class) == null
+ && field.getAnnotation(FindBys.class) == null
+ && field.getAnnotation(FindAll.class) == null){
+ return false;
+ }
+
+ return true;
+ }
+
+ private Object proxyForLocator(Field field, ElementLocator locator) {
+ ElementInterceptor elementInterceptor = new ElementInterceptor(locator);
+ return ProxyFactory.getEnhancedProxy(field.getType(),
+ elementInterceptor);
+ }
+
+ @SuppressWarnings("unchecked")
+ private List proxyForListLocator(
+ ElementLocator locator) {
+ ElementListInterceptor elementInterceptor = new ElementListInterceptor(locator);
+ return ProxyFactory.getEnhancedProxy(ArrayList.class,
+ elementInterceptor);
+ }
+
+ @Override
+ public void resetImplicitlyWaitTimeOut(long timeOut, TimeUnit timeUnit) {
+ factory.resetImplicitlyWaitTimeOut(timeOut, timeUnit);
+ }
+}
diff --git a/src/main/java/io/appium/java_client/pagefactory/ElementInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/ElementInterceptor.java
new file mode 100644
index 000000000..b028aa7a4
--- /dev/null
+++ b/src/main/java/io/appium/java_client/pagefactory/ElementInterceptor.java
@@ -0,0 +1,30 @@
+package io.appium.java_client.pagefactory;
+
+import io.appium.java_client.MobileElement;
+
+import java.lang.reflect.Method;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.pagefactory.ElementLocator;
+
+import net.sf.cglib.proxy.MethodInterceptor;
+import net.sf.cglib.proxy.MethodProxy;
+
+/**
+ * Intercepts requests to {@link MobileElement}
+ *
+ */
+class ElementInterceptor implements MethodInterceptor {
+ private final ElementLocator locator;
+
+ ElementInterceptor(ElementLocator locator) {
+ this.locator = locator;
+ }
+
+ public Object intercept(Object obj, Method method, Object[] args,
+ MethodProxy proxy) throws Throwable {
+ WebElement realElement = locator.findElement();
+ return method.invoke(realElement, args);
+ }
+
+}
diff --git a/src/main/java/io/appium/java_client/pagefactory/ElementListInterceptor.java b/src/main/java/io/appium/java_client/pagefactory/ElementListInterceptor.java
new file mode 100644
index 000000000..111ab5a49
--- /dev/null
+++ b/src/main/java/io/appium/java_client/pagefactory/ElementListInterceptor.java
@@ -0,0 +1,32 @@
+package io.appium.java_client.pagefactory;
+
+import io.appium.java_client.MobileElement;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.pagefactory.ElementLocator;
+
+import net.sf.cglib.proxy.MethodInterceptor;
+import net.sf.cglib.proxy.MethodProxy;
+
+/**
+ * Intercepts requests to the list of {@link MobileElement}
+ *
+ */
+class ElementListInterceptor implements MethodInterceptor{
+ private final ElementLocator locator;
+
+ ElementListInterceptor(ElementLocator locator){
+ this.locator = locator;
+ }
+
+ public Object intercept(Object obj, Method method, Object[] args,
+ MethodProxy proxy) throws Throwable {
+ ArrayList realElements = new ArrayList();
+ realElements.addAll(locator.findElements());
+ return method.invoke(realElements, args);
+ }
+
+}
diff --git a/src/main/java/io/appium/java_client/pagefactory/ProxyFactory.java b/src/main/java/io/appium/java_client/pagefactory/ProxyFactory.java
new file mode 100644
index 000000000..fc49f039a
--- /dev/null
+++ b/src/main/java/io/appium/java_client/pagefactory/ProxyFactory.java
@@ -0,0 +1,25 @@
+package io.appium.java_client.pagefactory;
+
+
+import net.sf.cglib.proxy.Enhancer;
+import net.sf.cglib.proxy.MethodInterceptor;
+
+/**
+ * Original class is a super class of a
+ * proxy object here
+ */
+abstract class ProxyFactory {
+
+ private ProxyFactory() {
+ super();
+ }
+
+ @SuppressWarnings("unchecked")
+ static T getEnhancedProxy(Class requiredClazz, MethodInterceptor interceptor){
+ Enhancer enhancer = new Enhancer();
+ enhancer.setSuperclass(requiredClazz);
+ enhancer.setCallback(interceptor);
+ return (T) enhancer.create(new Class>[] {}, new Object[] {});
+ }
+
+}
diff --git a/src/main/java/io/appium/java_client/pagefactory/ResetsImplicitlyWaitTimeOut.java b/src/main/java/io/appium/java_client/pagefactory/ResetsImplicitlyWaitTimeOut.java
new file mode 100644
index 000000000..8b676f231
--- /dev/null
+++ b/src/main/java/io/appium/java_client/pagefactory/ResetsImplicitlyWaitTimeOut.java
@@ -0,0 +1,7 @@
+package io.appium.java_client.pagefactory;
+
+import java.util.concurrent.TimeUnit;
+
+interface ResetsImplicitlyWaitTimeOut {
+ void resetImplicitlyWaitTimeOut(long timeOut, TimeUnit timeUnit);
+}
diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSFindBy.java b/src/main/java/io/appium/java_client/pagefactory/iOSFindBy.java
new file mode 100644
index 000000000..b9076ed9e
--- /dev/null
+++ b/src/main/java/io/appium/java_client/pagefactory/iOSFindBy.java
@@ -0,0 +1,26 @@
+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 iOS UI selectors, accessibility, id, name, class name, tag and xpath
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.TYPE})
+public @interface iOSFindBy {
+ String uiAutomator() default "";
+ String accessibility() default "";
+ 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/iOSFindBys.java b/src/main/java/io/appium/java_client/pagefactory/iOSFindBys.java
new file mode 100644
index 000000000..ae4ef5212
--- /dev/null
+++ b/src/main/java/io/appium/java_client/pagefactory/iOSFindBys.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 @iOSFindBy tags
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.TYPE})
+public @interface iOSFindBys {
+ iOSFindBy[] 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
new file mode 100644
index 000000000..7ca5a57a2
--- /dev/null
+++ b/src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java
@@ -0,0 +1,259 @@
+package io.appium.java_client.pagefactory_tests;
+
+import io.appium.java_client.AppiumDriver;
+import io.appium.java_client.MobileElement;
+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.iOSFindBy;
+import io.appium.java_client.pagefactory.iOSFindBys;
+import io.appium.java_client.remote.MobileCapabilityType;
+
+import java.io.File;
+import java.net.URL;
+import java.util.List;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.openqa.selenium.NoSuchElementException;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.remote.DesiredCapabilities;
+import org.openqa.selenium.remote.RemoteWebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.PageFactory;
+
+public class AndroidPageObjectTest {
+
+ private AppiumDriver driver;
+ @FindBy(className = "android.widget.TextView")
+ private List textVieWs;
+
+ @AndroidFindBy(className = "android.widget.TextView")
+ private List androidTextViews;
+
+ @iOSFindBy(uiAutomator = ".elements()[0]")
+ private List iosTextViews;
+
+ @iOSFindBy(uiAutomator = ".elements()[0]")
+ @AndroidFindBy(className = "android.widget.TextView")
+ private List androidOriOsTextViews;
+
+ @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/text1\")")
+ private List androidUIAutomatorViews;
+
+ @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/text1\")")
+ private List mobileElementViews;
+
+ @FindBy(className = "android.widget.TextView")
+ private List mobiletextVieWs;
+
+ @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/text1\")")
+ private List remoteElementViews;
+
+ @AndroidFindBys({
+ @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")"),
+ @AndroidFindBy(className = "android.widget.TextView")
+ })
+ private List chainElementViews;
+
+ @iOSFindBys({@iOSFindBy(uiAutomator = ".elements()[0]"),
+ @iOSFindBy(xpath = "//someElement")})
+ private List iosChainTextViews;
+
+ @AndroidFindBys({
+ @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")"),
+ @AndroidFindBy(id = "android:id/text1")
+ })
+ @iOSFindBys({@iOSFindBy(uiAutomator = ".elements()[0]"),
+ @iOSFindBy(xpath = "//someElement")})
+ private List chainAndroidOrIOSUIAutomatorViews;
+
+
+ @FindBy(id = "android:id/text1")
+ private WebElement textView;
+
+ @AndroidFindBy(className = "android.widget.TextView")
+ private WebElement androidTextView;
+
+ @iOSFindBy(uiAutomator = ".elements()[0]")
+ private WebElement iosTextView;
+
+ @AndroidFindBy(className = "android.widget.TextView")
+ @iOSFindBy(uiAutomator = ".elements()[0]")
+ private WebElement androidOriOsTextView;
+
+ @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/text1\")")
+ private WebElement androidUIAutomatorView;
+
+ @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/text1\")")
+ private MobileElement mobileElementView;
+
+ @FindBy(className = "android.widget.TextView")
+ private MobileElement mobiletextVieW;
+
+ @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/text1\")")
+ private RemoteWebElement remotetextVieW;
+
+ @AndroidFindBys({
+ @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")"),
+ @AndroidFindBy(className = "android.widget.TextView")
+ })
+ private WebElement chainElementView;
+
+ @iOSFindBys({@iOSFindBy(uiAutomator = ".elements()[0]"),
+ @iOSFindBy(xpath = "//someElement")})
+ private WebElement iosChainTextView;
+
+ @AndroidFindBys({
+ @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")"),
+ @AndroidFindBy(id = "android:id/text1")
+ })
+ @iOSFindBys({@iOSFindBy(uiAutomator = ".elements()[0]"),
+ @iOSFindBy(xpath = "//someElement")})
+ private WebElement chainAndroidOrIOSUIAutomatorView;
+
+ @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.BROWSER_NAME, "");
+ capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator");
+ capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
+ capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath());
+ driver = new AppiumDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities);
+
+ PageFactory.initElements(new AppiumFieldDecorator(driver), this);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ driver.quit();
+ }
+
+ @Test
+ public void findByElementsTest() {
+ Assert.assertNotEquals(0, textVieWs.size());
+ }
+
+ @Test
+ public void findByElementTest() {
+ Assert.assertNotEquals(null, textView.getAttribute("text"));
+ }
+
+
+ @Test
+ public void androidFindByElementsTest(){
+ Assert.assertNotEquals(0, androidTextViews.size());
+ }
+
+ @Test
+ public void androidFindByElementTest(){
+ Assert.assertNotEquals(null, androidTextView.getAttribute("text"));
+ }
+
+ @Test
+ public void checkThatElementsWereNotFoundByIOSUIAutomator(){
+ Assert.assertEquals(0, iosTextViews.size());
+ }
+
+ @Test
+ public void checkThatElementWasNotFoundByIOSUIAutomator(){
+ NoSuchElementException nsee = null;
+ try{
+ iosTextView.getAttribute("text");
+ }
+ catch (Exception e){
+ nsee = (NoSuchElementException) e;
+ }
+ Assert.assertNotNull(nsee);
+ }
+
+ @Test
+ public void androidOrIOSFindByElementsTest(){
+ Assert.assertNotEquals(0, androidOriOsTextViews.size());
+ }
+
+ @Test
+ public void androidOrIOSFindByElementTest(){
+ Assert.assertNotEquals(null, androidOriOsTextView.getAttribute("text"));
+ }
+
+ @Test
+ public void androidFindByUIAutomatorElementsTest(){
+ Assert.assertNotEquals(0, androidUIAutomatorViews.size());
+ }
+
+ @Test
+ public void androidFindByUIAutomatorElementTest(){
+ Assert.assertNotEquals(null, androidUIAutomatorView.getAttribute("text"));
+ }
+
+ @Test
+ public void areMobileElementsTest(){
+ Assert.assertNotEquals(0, mobileElementViews.size());
+ }
+
+ @Test
+ public void isMobileElementTest(){
+ Assert.assertNotEquals(null, mobileElementView.getAttribute("text"));
+ }
+
+ @Test
+ public void areMobileElements_FindByTest(){
+ Assert.assertNotEquals(0, mobiletextVieWs.size());
+ }
+
+ @Test
+ public void isMobileElement_FindByTest(){
+ Assert.assertNotEquals(null, mobiletextVieW.getAttribute("text"));
+ }
+
+ @Test
+ public void areRemoteElementsTest(){
+ Assert.assertNotEquals(0, remoteElementViews.size());
+ }
+
+ @Test
+ public void isRemoteElementTest(){
+ Assert.assertNotEquals(null, remotetextVieW.getAttribute("text"));
+ }
+
+ @Test
+ public void androidChainSearchElementsTest(){
+ Assert.assertNotEquals(0, chainElementViews.size());
+ }
+
+ @Test
+ public void androidChainSearchElementTest(){
+ Assert.assertNotEquals(null, chainElementView.getAttribute("text"));
+ }
+
+ @Test
+ public void checkThatElementsWereNotFoundByIOSUIAutomator_Chain(){
+ Assert.assertEquals(0, iosChainTextViews.size());
+ }
+
+ @Test
+ public void checkThatElementWasNotFoundByIOSUIAutomator_Chain(){
+ NoSuchElementException nsee = null;
+ try{
+ iosChainTextView.getAttribute("text");
+ }
+ catch (Exception e){
+ nsee = (NoSuchElementException) e;
+ }
+ Assert.assertNotNull(nsee);
+ }
+
+ @Test
+ public void androidOrIOSFindByElementsTest_ChainSearches(){
+ Assert.assertNotEquals(0, chainAndroidOrIOSUIAutomatorViews.size());
+ }
+
+ @Test
+ public void androidOrIOSFindByElementTest_ChainSearches(){
+ Assert.assertNotEquals(null, chainAndroidOrIOSUIAutomatorView.getAttribute("text"));
+ }
+}
diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/BrowserCompatibilityTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/BrowserCompatibilityTest.java
new file mode 100644
index 000000000..10ab92c76
--- /dev/null
+++ b/src/test/java/io/appium/java_client/pagefactory_tests/BrowserCompatibilityTest.java
@@ -0,0 +1,216 @@
+package io.appium.java_client.pagefactory_tests;
+
+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.iOSFindBy;
+import io.appium.java_client.pagefactory.iOSFindBys;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.Platform;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.chrome.ChromeDriver;
+import org.openqa.selenium.chrome.ChromeDriverService;
+import org.openqa.selenium.firefox.FirefoxDriver;
+import org.openqa.selenium.ie.InternetExplorerDriver;
+import org.openqa.selenium.ie.InternetExplorerDriverService;
+import org.openqa.selenium.safari.SafariDriver;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.FindBys;
+import org.openqa.selenium.support.PageFactory;
+import org.openqa.selenium.remote.RemoteWebElement;
+
+public class BrowserCompatibilityTest {
+
+ private static enum AvailableDrivers {
+ FIREFOX(FirefoxDriver.class, new ArrayList() {
+ private static final long serialVersionUID = 1L;
+ {
+ add(Platform.WINDOWS);
+ add(Platform.MAC);
+ }
+
+ }, new HashMap(), null), CHROME(ChromeDriver.class,
+ new ArrayList() {
+ private static final long serialVersionUID = 1L;
+ {
+ add(Platform.WINDOWS);
+ add(Platform.MAC);
+ }
+
+ }, new HashMap() {
+ private static final long serialVersionUID = 1L;
+
+ {
+ put(Platform.WINDOWS,
+ new File(
+ "src/test/java/io/appium/java_client/pagefactory_tests/chromedriver.exe"));
+ put(Platform.MAC,
+ new File(
+ "src/test/java/io/appium/java_client/pagefactory_tests/chromedriver"));
+ }
+ }, ChromeDriverService.CHROME_DRIVER_EXE_PROPERTY),
+ INTERNET_EXPLORER(
+ InternetExplorerDriver.class, new ArrayList() {
+ private static final long serialVersionUID = 1L;
+ {
+ add(Platform.WINDOWS);
+ }
+ }, new HashMap() {
+ private static final long serialVersionUID = 1L;
+
+ {
+ put(Platform.WINDOWS,
+ new File(
+ "src/test/java/io/appium/java_client/pagefactory_tests/IEDriverServer.exe"));
+ }
+ }, InternetExplorerDriverService.IE_DRIVER_EXE_PROPERTY), SAFARI(
+ SafariDriver.class, new ArrayList() {
+ private static final long serialVersionUID = 1L;
+ {
+ add(Platform.MAC);
+ }
+
+ }, new HashMap(), null);
+ // TODO Linux can be added if is necessary
+
+ private final Class extends WebDriver> driverClazz;
+ private final List platformCompatible;
+ private final Map serviceBinaries;
+ private final String propertyName;
+
+ private AvailableDrivers(Class extends WebDriver> driverClazz,
+ List platformCompatible,
+ Map serviceBinaries, String property) {
+ this.driverClazz = driverClazz;
+ this.platformCompatible = platformCompatible;
+ this.serviceBinaries = serviceBinaries;
+ this.propertyName = property;
+ }
+
+ private static AvailableDrivers getAvailableDriver(
+ Class extends WebDriver> driverClass, Platform p) {
+ AvailableDrivers[] availableDrivers = AvailableDrivers.values();
+ for (AvailableDrivers availableDriver : availableDrivers) {
+ if (!availableDriver.driverClazz.equals(driverClass)){
+ continue;
+ }
+
+ for (Platform compatible: availableDriver.platformCompatible){
+ if (p.is(compatible)){
+ return availableDriver;
+ }
+ }
+ }
+ return null;
+ }
+
+ private void setSystemProperty(Platform p) {
+ Platform platform = null;
+ for (Platform compatible: platformCompatible){
+ if (p.is(compatible)){
+ platform = compatible;
+ break;
+ }
+ }
+
+ if ((platform != null) && (propertyName != null)
+ && (serviceBinaries.get(platform) != null)) {
+ System.setProperty(propertyName, serviceBinaries.get(platform)
+ .getAbsolutePath());
+ }
+ }
+ }
+
+
+ public void setUp(Class extends WebDriver> driverClass) {
+ AvailableDrivers availableDriver = AvailableDrivers.getAvailableDriver(driverClass, current);
+ if (availableDriver != null){
+ availableDriver.setSystemProperty(current);
+ }
+ }
+
+ private final Platform current = Platform.getCurrent();
+ private final long IMPLICITLY_WAIT = 15;
+
+ @FindBy(name = "q")
+ @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/someId\")")
+ @iOSFindBy(uiAutomator = ".elements()[0]")
+ private WebElement searchTextField;
+
+ @AndroidFindBys({
+ @AndroidFindBy(className = "someClass"),
+ @AndroidFindBy(xpath = "//someTag")})
+ @iOSFindBys({
+ @iOSFindBy(xpath = "//selector[1]"),
+ @iOSFindBy(xpath = "//someTag")})
+ @FindBy(id="gbqfb")
+ private RemoteWebElement searchButton;
+
+ @AndroidFindBy(className = "someClass")
+ @iOSFindBys({
+ @iOSFindBy(xpath = "//selector[1]"),
+ @iOSFindBy(xpath = "//someTag")})
+ @FindBys({@FindBy(className = "r"), @FindBy(tagName = "a")})
+ private List foundLinks;
+
+ private void test(WebDriver driver){
+ try {
+ PageFactory.initElements(new AppiumFieldDecorator(driver, IMPLICITLY_WAIT, TimeUnit.SECONDS), this);
+ driver.get("https://www.google.com");
+
+ searchTextField.sendKeys("Hello");
+ searchButton.click();
+ Assert.assertNotEquals(0, foundLinks.size());
+ searchTextField.clear();
+ searchTextField.sendKeys("Hello, Appium!");
+ searchButton.click();
+ Assert.assertNotEquals(0, foundLinks.size());
+ } finally {
+ driver.quit();
+ }
+ }
+
+ @Test
+ public void fireFoxTest() {
+ if (AvailableDrivers.getAvailableDriver(FirefoxDriver.class, current)!=null){
+ setUp(FirefoxDriver.class);
+ test(new FirefoxDriver());
+ }
+ }
+
+ @Test
+ public void chromeTest() {
+ System.getProperty(ChromeDriverService.CHROME_DRIVER_EXE_PROPERTY);
+ if (AvailableDrivers.getAvailableDriver(ChromeDriver.class, current)!=null){
+ setUp(ChromeDriver.class);
+ test(new ChromeDriver());
+ }
+ }
+
+ @Test
+ public void ieTest() {
+ if (AvailableDrivers.getAvailableDriver(InternetExplorerDriver.class, current)!=null){
+ setUp(InternetExplorerDriver.class);
+ test(new InternetExplorerDriver());
+ }
+ }
+
+ @Test
+ public void safariTest() {
+ if (AvailableDrivers.getAvailableDriver(SafariDriver.class, current)!=null){
+ setUp(SafariDriver.class);
+ test(new SafariDriver());
+ }
+ }
+
+}
diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/IEDriverServer.exe b/src/test/java/io/appium/java_client/pagefactory_tests/IEDriverServer.exe
new file mode 100644
index 000000000..415561f9b
Binary files /dev/null and b/src/test/java/io/appium/java_client/pagefactory_tests/IEDriverServer.exe differ
diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/chromedriver b/src/test/java/io/appium/java_client/pagefactory_tests/chromedriver
new file mode 100755
index 000000000..d5eeb48ba
Binary files /dev/null and b/src/test/java/io/appium/java_client/pagefactory_tests/chromedriver differ
diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/chromedriver.exe b/src/test/java/io/appium/java_client/pagefactory_tests/chromedriver.exe
new file mode 100644
index 000000000..c295cc8ae
Binary files /dev/null and b/src/test/java/io/appium/java_client/pagefactory_tests/chromedriver.exe differ
diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/iOSPageObjectTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/iOSPageObjectTest.java
new file mode 100644
index 000000000..e9d228f2c
--- /dev/null
+++ b/src/test/java/io/appium/java_client/pagefactory_tests/iOSPageObjectTest.java
@@ -0,0 +1,216 @@
+package io.appium.java_client.pagefactory_tests;
+
+import io.appium.java_client.AppiumDriver;
+import io.appium.java_client.MobileElement;
+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.iOSFindBy;
+import io.appium.java_client.remote.MobileCapabilityType;
+
+import java.io.File;
+import java.net.URL;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.openqa.selenium.NoSuchElementException;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.remote.DesiredCapabilities;
+import org.openqa.selenium.remote.RemoteWebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.PageFactory;
+
+public class iOSPageObjectTest {
+
+ private AppiumDriver driver;
+ @FindBy(className = "UIAButton")
+ private List uiButtons;
+
+ @FindBy(className = "UIAButton")
+ private List iosUIButtons;
+
+ @iOSFindBy(uiAutomator = ".elements()[0]")
+ private List iosUIAutomatorButtons;
+
+ @iOSFindBy(uiAutomator = ".elements()[0]")
+ @AndroidFindBy(className = "android.widget.TextView")
+ private List androidOriOsTextViews;
+
+ @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/text1\")")
+ private List androidUIAutomatorViews;
+
+ @iOSFindBy(uiAutomator = ".elements()[0]")
+ private List mobileButtons;
+
+ @FindBy(className = "UIAButton")
+ private List mobiletFindBy_Buttons;
+
+ @iOSFindBy(uiAutomator = ".elements()[0]")
+ private List remoteElementViews;
+
+ @AndroidFindBys({
+ @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")"),
+ @AndroidFindBy(className = "android.widget.TextView")
+ })
+ private List chainElementViews;
+
+
+ @FindBy(className = "UIAButton")
+ private WebElement uiButton;
+
+ @FindBy(className = "UIAButton")
+ private WebElement iosUIButton;
+
+ @iOSFindBy(uiAutomator = ".elements()[0]")
+ private WebElement iosUIAutomatorButton;
+
+ @AndroidFindBy(className = "android.widget.TextView")
+ @iOSFindBy(uiAutomator = ".elements()[0]")
+ private WebElement androidOriOsTextView;
+
+ @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/text1\")")
+ private WebElement androidUIAutomatorView;
+
+ @iOSFindBy(uiAutomator = ".elements()[0]")
+ private MobileElement mobileButton;
+
+ @FindBy(className = "UIAButton")
+ private MobileElement mobiletFindBy_Button;
+
+ @iOSFindBy(uiAutomator = ".elements()[0]")
+ private RemoteWebElement remotetextVieW;
+
+ @AndroidFindBys({
+ @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")"),
+ @AndroidFindBy(className = "android.widget.TextView")
+ })
+ private WebElement chainElementView;
+
+ @Before
+ public void setUp() throws Exception {
+ File appDir = new File("src/test/java/io/appium/java_client");
+ File app = new File(appDir, "TestApp.app.zip");
+ DesiredCapabilities capabilities = new DesiredCapabilities();
+ capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, "");
+ capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "7.1");
+ capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, "iOS");
+ capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "iPhone Simulator");
+ capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath());
+ driver = new AppiumDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities);
+
+ PageFactory.initElements(new AppiumFieldDecorator(driver), this);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ driver.quit();
+ }
+
+ @Test
+ public void findByElementsTest() {
+ Assert.assertNotEquals(0, uiButtons.size());
+ }
+
+ @Test
+ public void findByElementTest() {
+ Assert.assertNotEquals(null, uiButton.getText());
+ }
+
+
+ @Test
+ public void iOSFindByElementsTest(){
+ Assert.assertNotEquals(0, iosUIButtons.size());
+ }
+
+ @Test
+ public void iosFindByElementTest(){
+ Assert.assertNotEquals(null, iosUIButton.getText());
+ }
+
+ @Test
+ public void checkThatElementsWereNotFoundByAndroidUIAutomator(){
+ Assert.assertEquals(0, androidUIAutomatorViews.size());
+ }
+
+ @Test
+ public void checkThatElementWasNotFoundByAndroidUIAutomator(){
+ NoSuchElementException nsee = null;
+ try{
+ androidUIAutomatorView.getText();
+ }
+ catch (Exception e){
+ nsee = (NoSuchElementException) e;
+ }
+ Assert.assertNotNull(nsee);
+ }
+
+ @Test
+ public void androidOrIOSFindByElementsTest(){
+ Assert.assertNotEquals(0, androidOriOsTextViews.size());
+ }
+
+ @Test
+ public void androidOrIOSFindByElementTest(){
+ Assert.assertNotEquals(null, androidOriOsTextView.getText());
+ }
+
+ @Test
+ public void iOSFindByUIAutomatorElementsTest(){
+ Assert.assertNotEquals(0, iosUIAutomatorButtons.size());
+ }
+
+ @Test
+ public void iOSFindByUIAutomatorElementTest(){
+ Assert.assertNotEquals(null, iosUIAutomatorButton.getText());
+ }
+
+ @Test
+ public void areMobileElementsTest(){
+ Assert.assertNotEquals(0, mobileButtons.size());
+ }
+
+ @Test
+ public void isMobileElementTest(){
+ Assert.assertNotEquals(null, mobileButton.getText());
+ }
+
+ @Test
+ public void areMobileElements_FindByTest(){
+ Assert.assertNotEquals(0, mobiletFindBy_Buttons.size());
+ }
+
+ @Test
+ public void isMobileElement_FindByTest(){
+ Assert.assertNotEquals(null, mobiletFindBy_Button.getText());
+ }
+
+ @Test
+ public void areRemoteElementsTest(){
+ Assert.assertNotEquals(0, remoteElementViews.size());
+ }
+
+ @Test
+ public void isRemoteElementTest(){
+ Assert.assertNotEquals(null, remotetextVieW.getText());
+ }
+
+ @Test
+ public void checkThatElementsWereNotFoundByAndroidUIAutomator_Chain(){
+ Assert.assertEquals(0, chainElementViews.size());
+ }
+
+ @Test
+ public void checkThatElementWasNotFoundByAndroidUIAutomator_Chain(){
+ NoSuchElementException nsee = null;
+ try{
+ chainElementView.getText();
+ }
+ catch (Exception e){
+ nsee = (NoSuchElementException) e;
+ }
+ Assert.assertNotNull(nsee);
+ }
+}