diff --git a/README.md b/README.md index 5a61f8751..97b874d49 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ You can get it on [WIKI](https://github.com/appium/java-client/wiki) - API with default implementation. PR [#470](https://github.com/appium/java-client/pull/470) - Tools that provide _Page Object_ engines were redesigned. The migration to [repeatable annotations](http://docs.oracle.com/javase/tutorial/java/annotations/repeating.html). Details you can read there: [#497](https://github.com/appium/java-client/pull/497). [Documentation was synced as well](https://github.com/appium/java-client/blob/master/docs/Page-objects.md#also-it-is-possible-to-define-chained-or-any-possible-locators). - **[MAJOR ENHANCEMENT]**: Migration from Maven to Gradle. Feature request is [#214](https://github.com/appium/java-client/issues/214). Fixes: [#442](https://github.com/appium/java-client/pull/442), [#465](https://github.com/appium/java-client/pull/465). -- **[MAJOR ENHANCEMENT]****[MAJOR REFACTORING]**. Non-abstract **AppiumDriver**: +- **[MAJOR ENHANCEMENT]** **[MAJOR REFACTORING]**. Non-abstract **AppiumDriver**: - Now the `io.appium.java_client.AppiumDriver` can use an instance of any `io.appium.java_client.MobileBy` subclass for the searching. It should work as expected when current session supports the given selector. It will throw `org.openqa.selenium.WebDriverException` otherwise. [#462](https://github.com/appium/java-client/pull/462) - The new interface `io.appium.java_client.FindsByFluentSelector` was added. [#462](https://github.com/appium/java-client/pull/462) - API was redesigned: @@ -129,14 +129,20 @@ You can get it on [WIKI](https://github.com/appium/java-client/wiki) - constructors of 'AppiumDriver' were re-designed. - constructors of 'AndroidDriver' were re-designed. - constructors of 'IOSDriver' were re-designed. - - _The work is not finished yet._ - + +- **[MAJOR ENHANCEMENT]** Windows automation. Epic [#471](https://github.com/appium/java-client/issues/471) + - The new interface `io.appium.java_client.FindsByWindowsAutomation` was added. [#462](https://github.com/appium/java-client/pull/462). With [@jonstoneman](https://github.com/jonstoneman) 's authorship. + - The new selector strategy `io.appium.java_client.MobileBy.ByWindowsAutomation` was added. [#462](https://github.com/appium/java-client/pull/462). With [@jonstoneman](https://github.com/jonstoneman) 's authorship. + - `io.appium.java_client.windows.WindowsDriver` was designed. [#538](https://github.com/appium/java-client/pull/538) + - `io.appium.java_client.windows.WindowsElement` was designed. [#538](https://github.com/appium/java-client/pull/538) + - `io.appium.java_client.windows.WindowsKeyCode ` was added. [#538](https://github.com/appium/java-client/pull/538) + - Page object tools were updated [#538](https://github.com/appium/java-client/pull/538) + - the `io.appium.java_client.pagefactory.WindowsFindBy` annotation was added. + - `io.appium.java_client.pagefactory.AppiumFieldDecorator` and supporting tools were actualized. + - **[MAJOR ENHANCEMENT]**: The new interface `io.appium.java_client.FindsByIosNSPredicate` was added. [#462](https://github.com/appium/java-client/pull/462). With [@rafael-chavez](https://github.com/rafael-chavez) 's authorship. It is implemented by `io.appium.java_client.ios.IOSDriver` and `io.appium.java_client.ios.IOSElement`. - **[MAJOR ENHANCEMENT]**: The new interface `io.appium.java_client.MobileBy.ByIosNsPredicate` was added. [#462](https://github.com/appium/java-client/pull/462). With [@rafael-chavez](https://github.com/rafael-chavez) 's authorship. -- **[MAJOR ENHANCEMENT]**: The new interface `io.appium.java_client.FindsByWindowsAutomation` was added. [#462](https://github.com/appium/java-client/pull/462). With [@jonstoneman](https://github.com/jonstoneman) 's authorship. -- **[MAJOR ENHANCEMENT]**: The new interface `io.appium.java_client.MobileBy.ByWindowsAutomation` was added. [#462](https://github.com/appium/java-client/pull/462). With [@jonstoneman](https://github.com/jonstoneman) 's authorship. - [ENHANCEMENT] Added the ability to set UiAutomator Congfigurator values. [#410](https://github.com/appium/java-client/pull/410). [#477](https://github.com/appium/java-client/pull/477). - **[UPDATE]** to Selenium 3.0. [#489](https://github.com/appium/java-client/pull/489) @@ -144,7 +150,8 @@ You can get it on [WIKI](https://github.com/appium/java-client/wiki) - [ENHANCEMENT]. TouchID Implementation (iOS Sim Only). Details: [#509](https://github.com/appium/java-client/pull/509) - [ENHANCEMENT]. The ability to use port, ip and log file as server arguments was provided. Feature request: [#521](https://github.com/appium/java-client/issues/521). Fixes: [#522](https://github.com/appium/java-client/issues/522), [#524](https://github.com/appium/java-client/issues/524). - [ENHANCEMENT]. The new interface ```io.appium.java_client.android.HasDeviceDetails``` was added. It is implemented by ```io.appium.java_client.android.AndroidDriver``` by default. [#518](https://github.com/appium/java-client/pull/518) -- [ENHANCEMENT].New touch actions were added. ```io.appium.java_client.ios.IOSTouchAction#doubleTap(WebElement, int, int)``` and ```io.appium.java_client.ios.IOSTouchAction#doubleTap(WebElement)```. [#523](https://github.com/appium/java-client/pull/523), [#444](https://github.com/appium/java-client/pull/444) +- [ENHANCEMENT]. New touch actions were added. ```io.appium.java_client.ios.IOSTouchAction#doubleTap(WebElement, int, int)``` and ```io.appium.java_client.ios.IOSTouchAction#doubleTap(WebElement)```. [#523](https://github.com/appium/java-client/pull/523), [#444](https://github.com/appium/java-client/pull/444) +- [ENHANCEMENT]. All constructors declared by `io.appium.java_client.AppiumDriver` are public now. - [BUG FIX]: There was the issue when "@WithTimeout" was changing general timeout of the waiting for elements. Bug report: [#467](https://github.com/appium/java-client/issues/467). Fixes: [#468](https://github.com/appium/java-client/issues/468), [#469](https://github.com/appium/java-client/issues/469), [#480](https://github.com/appium/java-client/issues/480). Read: [supported-settings](https://github.com/appium/appium/blob/master/docs/en/advanced-concepts/settings.md#supported-settings) - Added the server flag `io.appium.java_client.service.local.flags.AndroidServerFlag#REBOOT`. [#476](https://github.com/appium/java-client/pull/476) - Added `io.appium.java_client.remote.AndroidMobileCapabilityType.APP_WAIT_DURATION ` capability. [#461](https://github.com/appium/java-client/pull/461) diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 34eb92e49..b9bf03d03 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -79,7 +79,7 @@ public class AppiumDriver * @param capabilities take a look * at {@link org.openqa.selenium.Capabilities} */ - protected AppiumDriver(HttpCommandExecutor executor, Capabilities capabilities) { + public AppiumDriver(HttpCommandExecutor executor, Capabilities capabilities) { super(executor, capabilities); this.executeMethod = new AppiumExecutionMethod(this); locationContext = new RemoteLocationContext(executeMethod); @@ -335,7 +335,7 @@ public void zoom(int x, int y) { @Override public String getContext() { String contextName = String.valueOf(execute(DriverCommand.GET_CURRENT_CONTEXT_HANDLE).getValue()); - if (contextName.equals("null")) { + if ("null".equalsIgnoreCase(contextName)) { return null; } return contextName; diff --git a/src/main/java/io/appium/java_client/android/AndroidDriver.java b/src/main/java/io/appium/java_client/android/AndroidDriver.java index cab25c6e6..8b9bd93df 100644 --- a/src/main/java/io/appium/java_client/android/AndroidDriver.java +++ b/src/main/java/io/appium/java_client/android/AndroidDriver.java @@ -59,7 +59,7 @@ public class AndroidDriver * at {@link org.openqa.selenium.Capabilities} */ public AndroidDriver(HttpCommandExecutor executor, Capabilities capabilities) { - super(executor, capabilities); + super(executor, substituteMobilePlatform(capabilities, ANDROID_PLATFORM)); } /** diff --git a/src/main/java/io/appium/java_client/internal/ElementMap.java b/src/main/java/io/appium/java_client/internal/ElementMap.java index dfc7a6e06..3dacc2ce1 100644 --- a/src/main/java/io/appium/java_client/internal/ElementMap.java +++ b/src/main/java/io/appium/java_client/internal/ElementMap.java @@ -23,7 +23,9 @@ import io.appium.java_client.ios.IOSElement; import io.appium.java_client.remote.AutomationName; import io.appium.java_client.remote.MobilePlatform; +import io.appium.java_client.windows.WindowsElement; import io.appium.java_client.youiengine.YouiEngineElement; +import org.openqa.selenium.remote.RemoteWebElement; import java.util.Map; import java.util.Optional; @@ -34,7 +36,8 @@ public enum ElementMap { YOUI_ENGINE(AutomationName.YOUI_ENGINE.toLowerCase(), YouiEngineElement.class), IOS_XCUI_TEST(AutomationName.IOS_XCUI_TEST.toLowerCase(), IOSElement.class), ANDROID_UI_AUTOMATOR(MobilePlatform.ANDROID.toLowerCase(), AndroidElement.class), - IOS_UI_AUTOMATION(MobilePlatform.IOS.toLowerCase(), IOSElement.class); + IOS_UI_AUTOMATION(MobilePlatform.IOS.toLowerCase(), IOSElement.class), + WINDOWS(MobilePlatform.WINDOWS, WindowsElement.class); private static final Map mobileElementMap; @@ -50,7 +53,7 @@ public enum ElementMap { private final String platformOrAutomation; - private final Class elementClass; + private final Class elementClass; private ElementMap(String platformOrAutomation, Class elementClass) { this.platformOrAutomation = platformOrAutomation; @@ -61,7 +64,7 @@ public String getPlatformOrAutomation() { return platformOrAutomation; } - public Class getElementClass() { + public Class getElementClass() { return elementClass; } @@ -70,11 +73,12 @@ public Class getElementClass() { * @param automation automation name. * @return subclass of {@link io.appium.java_client.MobileElement} that convenient to current session details. */ - public static Class getElementClass(String platform, String automation) { - ElementMap element = Optional.ofNullable(mobileElementMap.get(automation)) - .orElse(mobileElementMap.get(platform)); + public static Class getElementClass(String platform, String automation) { + ElementMap element = Optional.ofNullable(mobileElementMap.get(String + .valueOf(automation).toLowerCase().trim())) + .orElse(mobileElementMap.get(String.valueOf(platform).toLowerCase().trim())); if (element == null) { - return null; + return RemoteWebElement.class; } return element.getElementClass(); } diff --git a/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java b/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java index 78f096825..50d27b02c 100644 --- a/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java +++ b/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java @@ -22,10 +22,9 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import io.appium.java_client.MobileElement; - import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.RemoteWebDriver; +import org.openqa.selenium.remote.RemoteWebElement; import org.openqa.selenium.remote.internal.JsonToWebElementConverter; import java.lang.reflect.Constructor; @@ -74,7 +73,7 @@ public Object apply(Object result) { if (result instanceof Map) { Map resultAsMap = (Map) result; if (resultAsMap.containsKey("ELEMENT")) { - MobileElement element = newMobileElement(); + RemoteWebElement element = newMobileElement(); element.setId(String.valueOf(resultAsMap.get("ELEMENT"))); element.setFileDetector(driver.getFileDetector()); return element; @@ -93,19 +92,13 @@ public Object apply(Object result) { return result; } - protected MobileElement newMobileElement() { - Class target = + protected RemoteWebElement newMobileElement() { + Class target = getElementClass(platform, automation); - - if (target == null) { - throw new WebDriverException(new ClassNotFoundException("The class of mobile element is " - + "unknown for current session")); - } - try { - Constructor constructor = target.getDeclaredConstructor(); + Constructor constructor = target.getDeclaredConstructor(); constructor.setAccessible(true); - MobileElement result = constructor.newInstance(); + RemoteWebElement result = constructor.newInstance(); result.setParent(driver); return result; } catch (Exception e) { diff --git a/src/main/java/io/appium/java_client/ios/IOSDriver.java b/src/main/java/io/appium/java_client/ios/IOSDriver.java index 97d61ed4e..ce71c5591 100644 --- a/src/main/java/io/appium/java_client/ios/IOSDriver.java +++ b/src/main/java/io/appium/java_client/ios/IOSDriver.java @@ -62,7 +62,7 @@ public class IOSDriver * at {@link org.openqa.selenium.Capabilities} */ public IOSDriver(HttpCommandExecutor executor, Capabilities capabilities) { - super(executor, capabilities); + super(executor, substituteMobilePlatform(capabilities, IOS_PLATFORM)); } /** 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 eb11f441c..c6acb55da 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java @@ -39,11 +39,11 @@ class AppiumElementLocator implements CacheableLocator { - final boolean shouldCache; - final By by; - final TimeOutDuration timeOutDuration; + private final boolean shouldCache; + private final By by; + private final TimeOutDuration timeOutDuration; private final TimeOutDuration originalTimeOutDuration; - final WebDriver originalWebDriver; + private final WebDriver originalWebDriver; private final SearchContext searchContext; private final WaitingFunction waitingFunction; private WebElement cachedElement; diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java index 29d7563b3..abd3e8773 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -28,6 +28,7 @@ import io.appium.java_client.ios.IOSElement; import io.appium.java_client.pagefactory.bys.ContentType; import io.appium.java_client.pagefactory.locator.CacheableLocator; +import io.appium.java_client.windows.WindowsElement; import org.openqa.selenium.SearchContext; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; @@ -43,7 +44,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.TimeUnit; /** @@ -69,6 +69,7 @@ public class AppiumFieldDecorator implements FieldDecorator { add(TouchableElement.class); add(AndroidElement.class); add(IOSElement.class); + add(WindowsElement.class); } }; @@ -91,13 +92,7 @@ private static String extractSessionData(WebDriver driver, String parameter) { return null; } - Object parameterValue = HasSessionDetails.class.cast(driver).getSessionDetail(parameter); - - if (parameterValue == null) { - return null; - } - - return String.valueOf(parameterValue).toLowerCase(); + return String.valueOf(HasSessionDetails.class.cast(driver).getSessionDetail(parameter)); } public AppiumFieldDecorator(SearchContext context, long implicitlyWaitTimeOut, @@ -147,15 +142,12 @@ protected List proxyForListLocator(ClassLoader ignored, Type listType = ((ParameterizedType) genericType).getActualTypeArguments()[0]; - boolean result = false; for (Class webElementClass : availableElementClasses) { - if (!webElementClass.equals(listType)) { - continue; + if (webElementClass.equals(listType)) { + return true; } - result = true; - break; } - return result; + return false; } }; @@ -235,14 +227,8 @@ private Object decorateWidget(Field field) { new WidgetInterceptor(locator, originalDriver, null, map, timeOutDuration)); } - private Class getTypeForProxy() { - Optional> optionalClass = - Optional.ofNullable(getElementClass(platform, automation)); - return optionalClass.orElse(RemoteWebElement.class); - } - private WebElement proxyForAnElement(ElementLocator locator) { ElementInterceptor elementInterceptor = new ElementInterceptor(locator, originalDriver); - return (WebElement) getEnhancedProxy(getTypeForProxy(), elementInterceptor); + return getEnhancedProxy(getElementClass(platform, automation), elementInterceptor); } } 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 70c94958b..217ae2b3a 100644 --- a/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java +++ b/src/main/java/io/appium/java_client/pagefactory/DefaultElementByBuilder.java @@ -32,6 +32,7 @@ import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; +import java.util.Optional; class DefaultElementByBuilder extends AppiumByBuilder { @@ -49,6 +50,19 @@ private static void checkDisallowedAnnotationPairs(Annotation a1, Annotation a2) } } + 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); + } + } + @Override protected void assertValidAnnotations() { AnnotatedElement annotatedElement = annotatedElementContainer.getAnnotated(); AndroidFindBy androidBy = annotatedElement.getAnnotation(AndroidFindBy.class); @@ -107,6 +121,8 @@ private static void checkDisallowedAnnotationPairs(Annotation a1, Annotation a2) @Override protected By buildMobileNativeBy() { AnnotatedElement annotatedElement = annotatedElementContainer.getAnnotated(); + HowToUseLocators howToUseLocators = annotatedElement.getAnnotation(HowToUseLocators.class); + if (isSelendroidAutomation()) { SelendroidFindBy[] selendroidFindByArray = annotatedElement.getAnnotationsByType(SelendroidFindBy.class); @@ -120,7 +136,6 @@ private static void checkDisallowedAnnotationPairs(Annotation a1, Annotation a2) return createBy(new Annotation[] {selendroidFindByArray[0]}, HowToUseSelectors.USE_ONE); } - //should be kept for some time if (selendroidFindBys != null) { return createBy(selendroidFindBys.value(), HowToUseSelectors.BUILD_CHAINED); } @@ -128,18 +143,11 @@ private static void checkDisallowedAnnotationPairs(Annotation a1, Annotation a2) if (selendroidFindByAll != null) { return createBy(selendroidFindByAll.value(), HowToUseSelectors.USE_ANY); } - + /////////////////////////////////////// + //code that supposed to be supported if (selendroidFindByArray != null && selendroidFindByArray.length > 0) { - HowToUseLocators howToUseLocators = annotatedElement.getAnnotation(HowToUseLocators.class); - if (howToUseLocators == null) { - return createBy(selendroidFindByArray, HowToUseSelectors.BUILD_CHAINED); - } - - if (howToUseLocators.selendroidAutomation() == LocatorGroupStrategy.ALL_POSSIBLE) { - return createBy(selendroidFindByArray, HowToUseSelectors.USE_ANY); - } - - return createBy(selendroidFindByArray, HowToUseSelectors.BUILD_CHAINED); + return buildMobileBy(howToUseLocators != null ? howToUseLocators.selendroidAutomation() : null, + selendroidFindByArray); } } @@ -153,7 +161,6 @@ private static void checkDisallowedAnnotationPairs(Annotation a1, Annotation a2) return createBy(new Annotation[] {androidFindByArray[0]}, HowToUseSelectors.USE_ONE); } - //should be kept for some time if (androidFindBys != null) { return createBy(androidFindBys.value(), HowToUseSelectors.BUILD_CHAINED); } @@ -161,18 +168,11 @@ private static void checkDisallowedAnnotationPairs(Annotation a1, Annotation a2) if (androidFindAll != null) { return createBy(androidFindAll.value(), HowToUseSelectors.USE_ANY); } - + /////////////////////////////////////// + //code that supposed to be supported if (androidFindByArray != null && androidFindByArray.length > 0) { - HowToUseLocators howToUseLocators = annotatedElement.getAnnotation(HowToUseLocators.class); - if (howToUseLocators == null) { - return createBy(androidFindByArray, HowToUseSelectors.BUILD_CHAINED); - } - - if (howToUseLocators.androidAutomation() == LocatorGroupStrategy.ALL_POSSIBLE) { - return createBy(androidFindByArray, HowToUseSelectors.USE_ANY); - } - - return createBy(androidFindByArray, HowToUseSelectors.BUILD_CHAINED); + return buildMobileBy(howToUseLocators != null ? howToUseLocators.androidAutomation() : null, + androidFindByArray); } } @@ -186,7 +186,6 @@ private static void checkDisallowedAnnotationPairs(Annotation a1, Annotation a2) return createBy(new Annotation[] {iOSFindByArray[0]}, HowToUseSelectors.USE_ONE); } - //should be kept for some time if (iOSFindBys != null) { return createBy(iOSFindBys.value(), HowToUseSelectors.BUILD_CHAINED); } @@ -194,18 +193,19 @@ private static void checkDisallowedAnnotationPairs(Annotation a1, Annotation a2) if (iOSFindAll != null) { return createBy(iOSFindAll.value(), HowToUseSelectors.USE_ANY); } - + /////////////////////////////////////// + //code that supposed to be supported if (iOSFindByArray != null && iOSFindByArray.length > 0) { - HowToUseLocators howToUseLocators = annotatedElement.getAnnotation(HowToUseLocators.class); - if (howToUseLocators == null) { - return createBy(iOSFindByArray, HowToUseSelectors.BUILD_CHAINED); - } + return buildMobileBy(howToUseLocators != null ? howToUseLocators.iOSAutomation() : null, + iOSFindByArray); + } + } - if (howToUseLocators.iOSAutomation() == LocatorGroupStrategy.ALL_POSSIBLE) { - return createBy(iOSFindByArray, HowToUseSelectors.USE_ANY); - } - - return createBy(iOSFindByArray, HowToUseSelectors.BUILD_CHAINED); + if (isWindows()) { + WindowsFindBy[] windowsFindByArray = annotatedElement.getAnnotationsByType(WindowsFindBy.class); + if (windowsFindByArray != null && windowsFindByArray.length > 0) { + return buildMobileBy(howToUseLocators != null ? howToUseLocators.windowsAutomation() : null, + windowsFindByArray); } } @@ -252,4 +252,4 @@ private By returnMappedBy(By byDefault, By nativeAppBy) { return returnMappedBy(defaultBy, mobileNativeBy); } -} +} \ No newline at end of file diff --git a/src/main/java/io/appium/java_client/pagefactory/HowToUseLocators.java b/src/main/java/io/appium/java_client/pagefactory/HowToUseLocators.java index 891ce1a1e..5b8affe94 100644 --- a/src/main/java/io/appium/java_client/pagefactory/HowToUseLocators.java +++ b/src/main/java/io/appium/java_client/pagefactory/HowToUseLocators.java @@ -43,4 +43,11 @@ * or the searching by all possible locators. */ LocatorGroupStrategy iOSAutomation() default LocatorGroupStrategy.CHAIN; + + /** + * @return the strategy which defines how to use locators which are described by + * the {@link WindowsFindBy} annotation. These annotations can define the chained searching + * or the searching by all possible locators. + */ + LocatorGroupStrategy windowsAutomation() default LocatorGroupStrategy.CHAIN; } diff --git a/src/main/java/io/appium/java_client/pagefactory/OverrideWidgetReader.java b/src/main/java/io/appium/java_client/pagefactory/OverrideWidgetReader.java index bfcc07a40..b433cb6b4 100644 --- a/src/main/java/io/appium/java_client/pagefactory/OverrideWidgetReader.java +++ b/src/main/java/io/appium/java_client/pagefactory/OverrideWidgetReader.java @@ -74,16 +74,16 @@ static Class getMobileNativeWidgetClass(Class getConstructorOfAMobileNativeWidget return findConvenientConstructor(clazz); } - static Map> read( + protected static Map> read( Class declaredClass, AnnotatedElement annotatedElement, String platform, String automation) { Map> result = new HashMap<>(); diff --git a/src/main/java/io/appium/java_client/pagefactory/ThrowableUtil.java b/src/main/java/io/appium/java_client/pagefactory/ThrowableUtil.java index f9b9dc024..ca65e9e24 100644 --- a/src/main/java/io/appium/java_client/pagefactory/ThrowableUtil.java +++ b/src/main/java/io/appium/java_client/pagefactory/ThrowableUtil.java @@ -24,7 +24,7 @@ class ThrowableUtil { private static final String INVALID_SELECTOR_PATTERN = "Invalid locator strategy:"; - static boolean isInvalidSelectorRootCause(Throwable e) { + protected static boolean isInvalidSelectorRootCause(Throwable e) { if (e == null) { return false; } @@ -41,7 +41,7 @@ static boolean isInvalidSelectorRootCause(Throwable e) { return isInvalidSelectorRootCause(e.getCause()); } - static boolean isStaleElementReferenceException(Throwable e) { + protected static boolean isStaleElementReferenceException(Throwable e) { if (e == null) { return false; } @@ -53,7 +53,7 @@ static boolean isStaleElementReferenceException(Throwable e) { return isStaleElementReferenceException(e.getCause()); } - static Throwable extractReadableException(Throwable e) { + protected static Throwable extractReadableException(Throwable e) { if (!RuntimeException.class.equals(e.getClass()) && !InvocationTargetException.class .equals(e.getClass())) { return e; diff --git a/src/main/java/io/appium/java_client/pagefactory/WidgetByBuilder.java b/src/main/java/io/appium/java_client/pagefactory/WidgetByBuilder.java index d5cffab4d..02247f1f8 100644 --- a/src/main/java/io/appium/java_client/pagefactory/WidgetByBuilder.java +++ b/src/main/java/io/appium/java_client/pagefactory/WidgetByBuilder.java @@ -26,6 +26,7 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.List; +import java.util.Optional; class WidgetByBuilder extends DefaultElementByBuilder { @@ -91,23 +92,13 @@ private By getByFromDeclaredClass(WhatIsNeeded whatIsNeeded) { } @Override protected By buildDefaultBy() { - By defaultBy = super.buildDefaultBy(); - - if (defaultBy != null) { - return defaultBy; - } else { - return getByFromDeclaredClass(WhatIsNeeded.DEFAULT_OR_HTML); - } + return Optional.ofNullable(super.buildDefaultBy()) + .orElse(getByFromDeclaredClass(WhatIsNeeded.DEFAULT_OR_HTML)); } @Override protected By buildMobileNativeBy() { - By mobileBy = super.buildMobileNativeBy(); - - if (mobileBy != null) { - return mobileBy; - } else { - return getByFromDeclaredClass(WhatIsNeeded.MOBILE_NATIVE); - } + return Optional.ofNullable(super.buildMobileNativeBy()) + .orElse(getByFromDeclaredClass(WhatIsNeeded.MOBILE_NATIVE)); } private enum WhatIsNeeded { diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java new file mode 100644 index 000000000..8a56f9898 --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBy.java @@ -0,0 +1,65 @@ +/* + * 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 java.lang.annotation.ElementType; +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 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 Windows automation selectors, accessibility, id, name, class name, tag and xpath + */ +@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) +@Repeatable(WindowsFindBySet.class) +public @interface WindowsFindBy { + + /** + * 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 ""; +} diff --git a/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java new file mode 100644 index 000000000..8512eed2b --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/WindowsFindBySet.java @@ -0,0 +1,36 @@ +/* + * 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 java.lang.annotation.ElementType; +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) +public @interface WindowsFindBySet { + /** + * @return an array of {@link WindowsFindBy} which builds a sequence of + * the chained searching for elements or a set of possible locators + */ + WindowsFindBy[] value(); +} 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 2f6d8e746..2c4f84f99 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 @@ -19,6 +19,7 @@ import static io.appium.java_client.remote.AutomationName.SELENDROID; import static io.appium.java_client.remote.MobilePlatform.ANDROID; import static io.appium.java_client.remote.MobilePlatform.IOS; +import static io.appium.java_client.remote.MobilePlatform.WINDOWS; import org.openqa.selenium.By; import org.openqa.selenium.support.pagefactory.AbstractAnnotations; @@ -40,7 +41,7 @@ * - https://code.google.com/p/selenium/wiki/PageFactory */ public abstract class AppiumByBuilder extends AbstractAnnotations { - static final Class[] DEFAULT_ANNOTATION_METHOD_ARGUMENTS = new Class[] {}; + protected static final Class[] DEFAULT_ANNOTATION_METHOD_ARGUMENTS = new Class[] {}; private static final List METHODS_TO_BE_EXCLUDED_WHEN_ANNOTATION_IS_READ = new ArrayList() { @@ -60,8 +61,8 @@ public abstract class AppiumByBuilder extends AbstractAnnotations { protected AppiumByBuilder(String platform, String automation) { this.annotatedElementContainer = new AnnotatedElementContainer(); - this.platform = String.valueOf(platform).toUpperCase().trim(); - this.automation = String.valueOf(automation).toUpperCase().trim(); + this.platform = String.valueOf(platform); + this.automation = String.valueOf(automation); } private static List getMethodNames(Method[] methods) { @@ -168,15 +169,19 @@ public void setAnnotated(AnnotatedElement annotated) { } protected boolean isAndroid() { - return ANDROID.toUpperCase().equals(platform); + return ANDROID.equalsIgnoreCase(platform); } protected boolean isSelendroidAutomation() { - return isAndroid() && SELENDROID.toUpperCase().equals(automation); + return isAndroid() && SELENDROID.equalsIgnoreCase(automation); } protected boolean isIOS() { - return IOS.toUpperCase().equals(platform); + return IOS.equalsIgnoreCase(platform); + } + + protected boolean isWindows() { + return WINDOWS.equalsIgnoreCase(platform); } /** 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 e712e41bb..9489311d2 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 @@ -84,6 +84,12 @@ enum Strategies { return By .partialLinkText(getValue(annotation, this)); } + }, + BYWINDOWSAUTOMATION("windowsAutomation") { + @Override By getBy(Annotation annotation) { + return MobileBy + .windowsAutomation(getValue(annotation, this)); + } }; private final String valueName; diff --git a/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java b/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java index 6d867c5a5..1636bbae1 100644 --- a/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java +++ b/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java @@ -289,7 +289,7 @@ public AppiumServiceBuilder withStartUpTimeOut(long time, TimeUnit timeUnit) { return this; } - void checkAppiumJS() { + private void checkAppiumJS() { if (appiumJS != null) { validateNodeStructure(appiumJS); return; diff --git a/src/main/java/io/appium/java_client/windows/WindowsDriver.java b/src/main/java/io/appium/java_client/windows/WindowsDriver.java new file mode 100644 index 000000000..5c03692ec --- /dev/null +++ b/src/main/java/io/appium/java_client/windows/WindowsDriver.java @@ -0,0 +1,75 @@ +/* + * 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.windows; + +import static io.appium.java_client.remote.MobilePlatform.WINDOWS; + +import io.appium.java_client.AppiumDriver; +import io.appium.java_client.FindsByWindowsAutomation; +import io.appium.java_client.HidesKeyboardWithKeyName; +import io.appium.java_client.PressesKeyCode; +import io.appium.java_client.service.local.AppiumDriverLocalService; +import io.appium.java_client.service.local.AppiumServiceBuilder; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.HttpCommandExecutor; +import org.openqa.selenium.remote.http.HttpClient; + +import java.net.URL; + +public class WindowsDriver + extends AppiumDriver implements PressesKeyCode, HidesKeyboardWithKeyName, + FindsByWindowsAutomation { + + public WindowsDriver(HttpCommandExecutor executor, Capabilities capabilities) { + super(executor, substituteMobilePlatform(capabilities, WINDOWS)); + } + + public WindowsDriver(URL remoteAddress, Capabilities desiredCapabilities) { + super(remoteAddress, substituteMobilePlatform(desiredCapabilities, WINDOWS)); + } + + public WindowsDriver(URL remoteAddress, HttpClient.Factory httpClientFactory, Capabilities desiredCapabilities) { + super(remoteAddress, httpClientFactory, substituteMobilePlatform(desiredCapabilities, WINDOWS)); + } + + public WindowsDriver(AppiumDriverLocalService service, Capabilities desiredCapabilities) { + super(service, substituteMobilePlatform(desiredCapabilities, WINDOWS)); + } + + public WindowsDriver(AppiumDriverLocalService service, HttpClient.Factory httpClientFactory, + Capabilities desiredCapabilities) { + super(service, httpClientFactory, substituteMobilePlatform(desiredCapabilities, WINDOWS)); + } + + public WindowsDriver(AppiumServiceBuilder builder, Capabilities desiredCapabilities) { + super(builder, substituteMobilePlatform(desiredCapabilities, WINDOWS)); + } + + public WindowsDriver(AppiumServiceBuilder builder, HttpClient.Factory httpClientFactory, + Capabilities desiredCapabilities) { + super(builder, httpClientFactory, substituteMobilePlatform(desiredCapabilities, WINDOWS)); + } + + public WindowsDriver(HttpClient.Factory httpClientFactory, Capabilities desiredCapabilities) { + super(httpClientFactory, substituteMobilePlatform(desiredCapabilities, WINDOWS)); + } + + public WindowsDriver(Capabilities desiredCapabilities) { + super(substituteMobilePlatform(desiredCapabilities, WINDOWS)); + } +} diff --git a/src/main/java/io/appium/java_client/windows/WindowsElement.java b/src/main/java/io/appium/java_client/windows/WindowsElement.java new file mode 100644 index 000000000..4f7ec7ba2 --- /dev/null +++ b/src/main/java/io/appium/java_client/windows/WindowsElement.java @@ -0,0 +1,23 @@ +/* + * 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.windows; + +import io.appium.java_client.FindsByWindowsAutomation; +import io.appium.java_client.MobileElement; + +public class WindowsElement extends MobileElement implements FindsByWindowsAutomation { +} diff --git a/src/main/java/io/appium/java_client/windows/WindowsKeyCode.java b/src/main/java/io/appium/java_client/windows/WindowsKeyCode.java new file mode 100644 index 000000000..c2968291f --- /dev/null +++ b/src/main/java/io/appium/java_client/windows/WindowsKeyCode.java @@ -0,0 +1,41 @@ +/* + * 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.windows; + +/** + * Created by STikhomirov on 15.12.2016. + */ +public interface WindowsKeyCode { + int POWER = 0; + int WINDOWS = 1; + int VOLUME_UP = 2; + int VOLUME_DOWN = 3; + int ROTATION_LOCK = 4; + int COUNT_MIN = 5; + int BACK = 5; + int SEARCH = 6; + int CAMERA_FOCUS = 7; + int CAMERA_SHUTTER = 8; + int RINGER_TOGGLE = 9; + int HEAD_SET = 10; + int HWKB_DPLOY = 11; + int CAMERA_LENS = 12; + int OEM_CUSTOM = 13; + int OEM_CUSTOM2 = 14; + int OEM_CUSTOM3 = 15; + int COUNT = 16; +} 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 9eee1e08f..638cb40cf 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 @@ -235,11 +235,11 @@ public class AndroidPageObjectTest extends BaseAndroidTest { assertNotEquals(null, mobileElementView.getAttribute("text")); } - @Test public void areMobileElements_FindByTest() { + @Test public void areMobileElementsFindByTest() { assertNotEquals(0, mobiletextVieWs.size()); } - @Test public void isMobileElement_FindByTest() { + @Test public void isMobileElementFindByTest() { assertNotEquals(null, mobiletextVieW.getAttribute("text")); } @@ -259,11 +259,11 @@ public class AndroidPageObjectTest extends BaseAndroidTest { assertNotEquals(null, chainElementView.getAttribute("text")); } - @Test public void checkThatElementsWereNotFoundByIOSUIAutomator_Chain() { + @Test public void checkThatElementsWereNotFoundByIOSUIAutomatorChain() { assertEquals(0, iosChainTextViews.size()); } - @Test public void checkThatElementWasNotFoundByIOSUIAutomator_Chain() { + @Test public void checkThatElementWasNotFoundByIOSUIAutomatorChain() { NoSuchElementException nsee = null; try { iosChainTextView.getAttribute("text"); @@ -273,11 +273,11 @@ public class AndroidPageObjectTest extends BaseAndroidTest { assertNotNull(nsee); } - @Test public void androidOrIOSFindByElementsTest_ChainSearches() { + @Test public void androidOrIOSFindByElementsTestChainSearches() { assertNotEquals(0, chainAndroidOrIOSUIAutomatorViews.size()); } - @Test public void androidOrIOSFindByElementTest_ChainSearches() { + @Test public void androidOrIOSFindByElementTestChainSearches() { assertNotEquals(null, chainAndroidOrIOSUIAutomatorView.getAttribute("text")); } @@ -311,7 +311,7 @@ public class AndroidPageObjectTest extends BaseAndroidTest { throw new RuntimeException(NoSuchElementException.class.getName() + " has been expected."); } - @Test public void checkThatTestWillNotBeFailedBecauseOfInvalidFindBy_List() { + @Test public void checkThatTestWillNotBeFailedBecauseOfInvalidFindByList() { assertEquals(0, elementsWhenAndroidLocatorIsNotDefinedAndThereIsInvalidFindBy.size()); } diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/IOSPageFactoryTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/IOSPageFactoryTest.java index 16321ee76..a32131a32 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/IOSPageFactoryTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/IOSPageFactoryTest.java @@ -200,11 +200,11 @@ public class IOSPageFactoryTest extends AppIOSTest { assertNotEquals(null, mobileButton.getText()); } - @Test public void areMobileElements_FindByTest() { + @Test public void areMobileElementsFindByTest() { assertNotEquals(0, mobiletFindByButtons.size()); } - @Test public void isMobileElement_FindByTest() { + @Test public void isMobileElementFindByTest() { assertNotEquals(null, mobiletFindByButton.getText()); } @@ -216,11 +216,11 @@ public class IOSPageFactoryTest extends AppIOSTest { assertNotEquals(null, remotetextVieW.getText()); } - @Test public void checkThatElementsWereNotFoundByAndroidUIAutomator_Chain() { + @Test public void checkThatElementsWereNotFoundByAndroidUIAutomatorChain() { assertEquals(0, chainElementViews.size()); } - @Test public void checkThatElementWasNotFoundByAndroidUIAutomator_Chain() { + @Test public void checkThatElementWasNotFoundByAndroidUIAutomatorChain() { NoSuchElementException nsee = null; try { chainElementView.getText(); @@ -234,7 +234,7 @@ public class IOSPageFactoryTest extends AppIOSTest { assertNotEquals(null, iosButton.getText()); } - @Test public void areIOSElements_FindByTest() { + @Test public void areIOSElementsFindByTest() { assertNotEquals(0, iosButtons.size()); } @@ -257,7 +257,7 @@ public class IOSPageFactoryTest extends AppIOSTest { throw new RuntimeException(NoSuchElementException.class.getName() + " has been expected."); } - @Test public void checkThatTestWillNotBeFailedBecauseOfInvalidFindBy_List() { + @Test public void checkThatTestWillNotBeFailedBecauseOfInvalidFindByList() { assertEquals(0, elementsWhenAndroidLocatorIsNotDefinedAndThereIsInvalidFindBy.size()); } }