diff --git a/README.md b/README.md index 03de8989a..423048fc0 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,18 @@ Depends upon the Selenium Java client library, available [here](http://docs.sele io.appium java-client - 1.7.0 + 2.0.0 ``` Javadocs: http://appium.github.io/java-client/ +###Structure### + +There is an abstract _AppiumDriver_ class which inherits from the Selenium Java Client. +The _AppiumDriver_ class contains all methods shared by iOS and Android. +_IOSDriver_ and _AndroidDriver_ both extend _AppiumDriver_ and provide more methods, and specific implementations for some methods. + ###Added functions### More can be found in the docs, but here's a quick list of features which this project has added to the usual selenium binding. @@ -64,6 +70,11 @@ Locators: - findElementsByAndroidUIAutomator() ##Changelog## +*2.0.0* +- AppiumDriver is now an abstract class, use IOSDriver and AndroidDriver which both extend it. You no longer need to include the `PLATFORM_NAME` desired capability since it's automatic for each class. Thanks to @TikhomirovSergey for all their work +- ScrollTo() and ScrollToExact() methods reimplemented +- Zoom() and Pinch() are now a little smarter and less likely to fail if you element is near the edge of the screen. Congratulate @BJap on their first PR! + *1.7.0* - Removed `scrollTo()` and `scrollToExact()` methods because they relied on `complexFind()`. They will be added back in the next version! - Removed `complexFind()` diff --git a/java-client.iml b/java-client.iml index 9578231aa..f5d58ed86 100644 --- a/java-client.iml +++ b/java-client.iml @@ -19,12 +19,12 @@ - - - + + + - + @@ -33,71 +33,30 @@ - - + + - - - + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + diff --git a/pom.xml b/pom.xml index a1dae214f..fff038a23 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.appium java-client - 1.7.0 + 2.0.0 com.google.code.gson @@ -15,7 +15,7 @@ org.seleniumhq.selenium selenium-java - 2.42.2 + 2.43.1 junit @@ -37,7 +37,21 @@ cglib 3.1 - + + org.reflections + reflections + 0.9.8 + + + guava + com.google.guava + + + xml-apis + xml-apis + + + jar java-client diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 486fb8302..9193e7977 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -21,7 +21,7 @@ import com.google.common.collect.ImmutableMap; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import io.appium.java_client.internal.JsonToMobileElementConverter; +import io.appium.java_client.remote.MobileCapabilityType; import org.openqa.selenium.*; import org.openqa.selenium.html5.Location; import org.openqa.selenium.html5.LocationContext; @@ -36,354 +36,324 @@ import java.util.Map; import java.util.Set; -import static com.google.common.base.Preconditions.checkArgument; -import static io.appium.java_client.remote.MobileCapabilityType.*; import static io.appium.java_client.MobileCommand.*; -public class AppiumDriver extends RemoteWebDriver implements MobileDriver, ContextAware, Rotatable, FindsByIosUIAutomation, - FindsByAndroidUIAutomator, FindsByAccessibilityId, LocationContext { - - private final static ErrorHandler errorHandler = new ErrorHandler(new ErrorCodesMobile(), true); - private URL remoteAddress; - private RemoteLocationContext locationContext; - private ExecuteMethod executeMethod; - - public AppiumDriver(URL remoteAddress, Capabilities desiredCapabilities){ - - super(remoteAddress, desiredCapabilities); - this.setElementConverter(new JsonToMobileElementConverter(this)); - - this.executeMethod = new AppiumExecutionMethod(this); - this.remoteAddress = remoteAddress; - locationContext = new RemoteLocationContext(executeMethod); - - ImmutableMap.Builder builder = ImmutableMap.builder(); - builder - .put(RESET, postC("/session/:sessionId/appium/app/reset")) - .put(GET_STRINGS, postC("/session/:sessionId/appium/app/strings")) - .put(KEY_EVENT, postC("/session/:sessionId/appium/device/keyevent")) - .put(CURRENT_ACTIVITY, getC("/session/:sessionId/appium/device/current_activity")) - .put(SET_VALUE, postC("/session/:sessionId/appium/element/:id/value")) - .put(PULL_FILE, postC("/session/:sessionId/appium/device/pull_file")) - .put(PULL_FOLDER, postC("/session/:sessionId/appium/device/pull_folder")) - .put(HIDE_KEYBOARD, postC("/session/:sessionId/appium/device/hide_keyboard")) - .put(PUSH_FILE, postC("/session/:sessionId/appium/device/push_file")) - .put(RUN_APP_IN_BACKGROUND, postC("/session/:sessionId/appium/app/background")) - .put(PERFORM_TOUCH_ACTION, postC("/session/:sessionId/touch/perform")) - .put(PERFORM_MULTI_TOUCH, postC("/session/:sessionId/touch/multi/perform")) - .put(IS_APP_INSTALLED, postC("/session/:sessionId/appium/device/app_installed")) - .put(INSTALL_APP, postC("/session/:sessionId/appium/device/install_app")) - .put(REMOVE_APP, postC("/session/:sessionId/appium/device/remove_app")) - .put(LAUNCH_APP, postC("/session/:sessionId/appium/app/launch")) - .put(CLOSE_APP, postC("/session/:sessionId/appium/app/close")) - .put(END_TEST_COVERAGE, postC("/session/:sessionId/appium/app/end_test_coverage")) - .put(LOCK, postC("/session/:sessionId/appium/device/lock")) - .put(IS_LOCKED, postC("/session/:sessionId/appium/device/is_locked")) - .put(SHAKE, postC("/session/:sessionId/appium/device/shake")) - .put(COMPLEX_FIND, postC("/session/:sessionId/appium/app/complex_find")) - .put(OPEN_NOTIFICATIONS, postC("/session/:sessionId/appium/device/open_notifications")) - .put(GET_NETWORK_CONNECTION, getC("/session/:sessionId/network_connection")) - .put(SET_NETWORK_CONNECTION, postC("/session/:sessionId/network_connection")) - .put(GET_SETTINGS, getC("/session/:sessionId/appium/settings")) - .put(SET_SETTINGS, postC("/session/:sessionId/appium/settings")) - .put(START_ACTIVITY, postC("/session/:sessionId/appium/device/start_activity")) - ; - ImmutableMap mobileCommands = builder.build(); - - HttpCommandExecutor mobileExecutor = new HttpCommandExecutor(mobileCommands, remoteAddress); - super.setCommandExecutor(mobileExecutor); - - super.setErrorHandler(errorHandler); - } - - @Override - public Response execute(String driverCommand, Map parameters) { - - return super.execute(driverCommand, parameters); - } - - @Override - protected Response execute(String command) { - return execute(command, ImmutableMap.of()); - } - - @Override - public ExecuteMethod getExecuteMethod() { - return executeMethod; - } - - /** - * Reset the currently running app for this session - */ - public void resetApp() { - execute(MobileCommand.RESET); - } - - /** - * Get all defined Strings from an Android app for the default language - * - * @return a string of all the localized strings defined in the app - */ - public String getAppStrings() { - Response response = execute(GET_STRINGS); - return response.getValue().toString(); - } - - /** - * Get all defined Strings from an Android app for the specified language - * - * @param language strings language code - * @return a string of all the localized strings defined in the app - */ - public String getAppStrings(String language) { - ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.put("language", language); - ImmutableMap parameters = builder.build(); - Response response = execute(GET_STRINGS, parameters); - return response.getValue().toString(); - } - - /** - * Send a key event to the device - * - * @param key code for the key pressed on the device - */ - public void sendKeyEvent(int key) { - sendKeyEvent(key, null); - } - - /** - * Send a key event along with an Android metastate to an Android device - * Metastates are things like *shift* to get uppercase characters - * - * @param key code for the key pressed on the Android device - * @param metastate metastate for the keypress - */ - public void sendKeyEvent(int key, Integer metastate) { - ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.put("keycode", key); - if (metastate != null) { builder.put("metastate", metastate); } - ImmutableMap parameters = builder.build(); - execute(KEY_EVENT, parameters); - } - - /** - * Get the current activity being run on the mobile device - */ - public String currentActivity() { - Response response = execute(CURRENT_ACTIVITY); - return response.getValue().toString(); - } - - /** - * Launches an arbitrary activity during a test. If the activity belongs to - * another application, that application is started and the activity is opened. - * - * This is an Android-only method. - * @param appPackage The package containing the activity. [Required] - * @param appActivity The activity to start. [Required] - * @param appWaitPackage Automation will begin after this package starts. [Optional] - * @param appWaitActivity Automation will begin after this activity starts. [Optional] - * @example - * driver.startActivity("com.foo.bar", ".MyActivity", null, null); - */ - public void startActivity(String appPackage, String appActivity, String appWaitPackage, String appWaitActivity) - throws IllegalArgumentException { - - checkArgument((_isNotNullOrEmpty(appPackage) && _isNotNullOrEmpty(appActivity)), - String.format("'%s' and '%s' are required.", APP_PACKAGE, APP_ACTIVITY)); - - appWaitPackage = _isNotNullOrEmpty(appWaitPackage) ? appWaitPackage : ""; - appWaitActivity = _isNotNullOrEmpty(appWaitActivity) ? appWaitActivity : ""; - - ImmutableMap parameters = ImmutableMap.of(APP_PACKAGE, appPackage, - APP_ACTIVITY, appActivity, - APP_WAIT_PACKAGE, appWaitPackage, - APP_WAIT_ACTIVITY, appWaitActivity); - - execute(START_ACTIVITY, parameters); - } - - /** - * Checks if a string is null, empty, or whitespace. - * - * @param str String to check. - * - * @return True if str is not null or empty. - */ - private static boolean _isNotNullOrEmpty(String str) { - return str != null && !str.isEmpty() && str.trim().length() > 0; - } - - /** - * - * @param remotePath On Android and iOS, this is either the path to the file (relative to the root of the app's file system). - * On iOS only, if path starts with /AppName.app, which will be replaced with the application's .app directory - * @return A byte array of Base64 encoded data. - */ - public byte[] pullFile(String remotePath) { - Response response = execute(PULL_FILE, ImmutableMap.of("path", remotePath)); - String base64String = response.getValue().toString(); - - return DatatypeConverter.parseBase64Binary(base64String); - } - - /** - * Save base64 encoded data as a file on the remote mobile device. - * This is an Android only method. - * @param remotePath Path to file to write data to on remote device - * @param base64Data Base64 encoded byte array of data to write to remote device - */ - public void pushFile(String remotePath, byte[] base64Data) { - ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.put("path", remotePath).put("data", base64Data); - execute(PUSH_FILE, builder.build()); - } - - /** - * Pull a folder from the simulator/device. Does not work on iOS Real Devices, but works on simulators - * - * @param remotePath On Android and iOS, this is either the path to the file (relative to the root of the app's file system). - * On iOS only, if path starts with /AppName.app, which will be replaced with the application's .app directory - * @return A byte array of Base64 encoded data, representing a ZIP ARCHIVE of the contents of the requested folder. - */ - public byte[] pullFolder(String remotePath) { - Response response = execute(PULL_FOLDER, ImmutableMap.of("path", remotePath)); - String base64String = response.getValue().toString(); - - return DatatypeConverter.parseBase64Binary(base64String); - } - - /** - * Hides the keyboard if it is showing. - * On iOS, there are multiple strategies for hiding the keyboard. Defaults to the "tapOutside" strategy (taps outside the keyboard). - * Switch to using hideKeyboard(HideKeyboardStrategy.PRESS_KEY, "Done") if this doesn't work. - */ - public void hideKeyboard() { - execute(HIDE_KEYBOARD); - } - - /** - * Hides the keyboard if it is showing. Available strategies are PRESS_KEY and TAP_OUTSIDE. - * One taps outside the keyboard, the other presses a key of your choosing (probably the 'Done' key). - * Hiding the keyboard often depends on the way an app is implemented, no single strategy always works. - * - * These parameters are only for iOS, and ignored by Android. - * - * @param strategy HideKeyboardStrategy - * @param keyName a String, representing the text displayed on the button of the keyboard you want to press. For example: "Done" - */ - public void hideKeyboard(String strategy, String keyName) { - ImmutableMap parameters = ImmutableMap.of("strategy", strategy); - if (_isNotNullOrEmpty(keyName)) { - parameters = parameters.of("key", keyName); - } - - execute(HIDE_KEYBOARD, parameters); - } - - /** - * Hides the keyboard by pressing the button specified by keyName if it is showing. - * This is an iOS only command. - * @param keyName The button pressed by the mobile driver to attempt hiding the keyboard - */ - public void hideKeyboard(String keyName) { - execute(HIDE_KEYBOARD, ImmutableMap.of("keyName", keyName)); - } - - /** - * Runs the current app as a background app for the number of seconds requested. - * This is a synchronous method, it returns after the back has been returned to the foreground. - * @param seconds Number of seconds to run App in background - */ - public void runAppInBackground(int seconds) { - execute(RUN_APP_IN_BACKGROUND, ImmutableMap.of("seconds", seconds)); - } - - /** - * Open the notification shade, on Android devices. - * Android only method. - */ - public void openNotifications() { execute(OPEN_NOTIFICATIONS); } - /** - * Performs a chain of touch actions, which together can be considered an entire gesture. - * See the Webriver 3 spec https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html - * - * It's more convenient to call the perform() method of the TouchAction object itself. - * - * @param touchAction A TouchAction object, which contains a list of individual touch actions to perform - * @return the same touchaction object - */ - public TouchAction performTouchAction(TouchAction touchAction) { - ImmutableMap parameters = touchAction.getParameters(); - touchAction.clearParameters(); - execute(PERFORM_TOUCH_ACTION, parameters); - - return touchAction; - } - - /** - * Performs multiple TouchAction gestures at the same time, to simulate multiple fingers/touch inputs. - * See the Webriver 3 spec https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html - * - * It's more convenient to call the perform() method of the MultiTouchAction object. - * - * @param multiAction the MultiTouchAction object to perform. - */ - public void performMultiTouchAction(MultiTouchAction multiAction) { - ImmutableMap parameters = multiAction.getParameters(); - execute(PERFORM_MULTI_TOUCH, parameters); - } - - /** - * Convenience method for tapping the center of an element on the screen - * @param fingers number of fingers/appendages to tap with - * @param element element to tap - * @param duration how long between pressing down, and lifting fingers/appendages - */ - public void tap(int fingers, WebElement element, int duration) { - MultiTouchAction multiTouch = new MultiTouchAction(this); - - for (int i = 0; i < fingers; i++) { - multiTouch.add(createTap(element, duration)); - } - - multiTouch.perform(); - } - - /** - * Convenience method for tapping a position on the screen - * @param fingers number of fingers/appendages to tap with - * @param x x coordinate - * @param y y coordinate - * @param duration - */ - public void tap(int fingers, int x, int y, int duration) { - MultiTouchAction multiTouch = new MultiTouchAction(this); - - for (int i = 0; i < fingers; i++) { - multiTouch.add(createTap(x, y, duration)); - } - - multiTouch.perform(); - } - - /** - * Convenience method for swiping across the screen - * @param startx starting x coordinate - * @param starty starting y coordinate - * @param endx ending x coordinate - * @param endy ending y coordinate - * @param duration amount of time in milliseconds for the entire swipe action to take - */ - public void swipe(int startx, int starty, int endx, int endy, int duration) { - TouchAction touchAction = new TouchAction(this); - - //appium converts press-wait-moveto-release to a swipe action - touchAction.press(startx, starty).waitAction(duration).moveTo(endx, endy).release(); - - touchAction.perform(); - } +public abstract class AppiumDriver extends RemoteWebDriver implements MobileDriver, + ContextAware, Rotatable, FindsByAccessibilityId, LocationContext, + DeviceActionShortcuts, TouchShortcuts, InteractsWithFiles, + InteractsWithApps, ScrollsTo { + + private final static ErrorHandler errorHandler = new ErrorHandler( + new ErrorCodesMobile(), true); + private URL remoteAddress; + private RemoteLocationContext locationContext; + private ExecuteMethod executeMethod; + + // frequently used command parameters + protected final String KEY_CODE = "keycode"; + protected final String PATH = "path"; + private final String SETTINGS = "settings"; + + /** + * @param originalCapabilities + * the given {@link Capabilities} + * @param newPlatform + * a {@link MobileCapabilityType#PLATFORM_NAME} value which has + * to be set up + * @return {@link Capabilities} with changed mobile platform value + */ + protected static Capabilities substituteMobilePlatform( + Capabilities originalCapabilities, String newPlatform) { + DesiredCapabilities dc = new DesiredCapabilities(originalCapabilities); + dc.setCapability(MobileCapabilityType.PLATFORM_NAME, newPlatform); + return dc; + } + + /** + * @param param + * is a parameter name + * @param value + * is the parameter value + * @return built {@link ImmutableMap} + */ + protected static ImmutableMap getCommandImmutableMap( + String param, Object value) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + builder.put(param, value); + return builder.build(); + } + + /** + * + * @param params + * is the array with parameter names + * @param values + * is the array with parameter values + * @return built {@link ImmutableMap} + */ + protected static ImmutableMap getCommandImmutableMap( + String[] params, Object[] values) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (int i = 0; i < params.length; i++) { + if (_isNotNullOrEmpty(params[i]) && _isNotNullOrEmpty(values[i])) { + builder.put(params[i], values[i]); + } + } + return builder.build(); + } + + public AppiumDriver(URL remoteAddress, Capabilities desiredCapabilities) { + + super(remoteAddress, desiredCapabilities); + + this.executeMethod = new AppiumExecutionMethod(this); + this.remoteAddress = remoteAddress; + locationContext = new RemoteLocationContext(executeMethod); + + ImmutableMap.Builder builder = ImmutableMap + .builder(); + builder.put(RESET, postC("/session/:sessionId/appium/app/reset")) + .put(GET_STRINGS, + postC("/session/:sessionId/appium/app/strings")) + .put(KEY_EVENT, + postC("/session/:sessionId/appium/device/keyevent")) + .put(CURRENT_ACTIVITY, + getC("/session/:sessionId/appium/device/current_activity")) + .put(SET_VALUE, + postC("/session/:sessionId/appium/element/:id/value")) + .put(PULL_FILE, + postC("/session/:sessionId/appium/device/pull_file")) + .put(PULL_FOLDER, + postC("/session/:sessionId/appium/device/pull_folder")) + .put(HIDE_KEYBOARD, + postC("/session/:sessionId/appium/device/hide_keyboard")) + .put(PUSH_FILE, + postC("/session/:sessionId/appium/device/push_file")) + .put(RUN_APP_IN_BACKGROUND, + postC("/session/:sessionId/appium/app/background")) + .put(PERFORM_TOUCH_ACTION, + postC("/session/:sessionId/touch/perform")) + .put(PERFORM_MULTI_TOUCH, + postC("/session/:sessionId/touch/multi/perform")) + .put(IS_APP_INSTALLED, + postC("/session/:sessionId/appium/device/app_installed")) + .put(INSTALL_APP, + postC("/session/:sessionId/appium/device/install_app")) + .put(REMOVE_APP, + postC("/session/:sessionId/appium/device/remove_app")) + .put(LAUNCH_APP, postC("/session/:sessionId/appium/app/launch")) + .put(CLOSE_APP, postC("/session/:sessionId/appium/app/close")) + .put(END_TEST_COVERAGE, + postC("/session/:sessionId/appium/app/end_test_coverage")) + .put(LOCK, postC("/session/:sessionId/appium/device/lock")) + .put(IS_LOCKED, + postC("/session/:sessionId/appium/device/is_locked")) + .put(SHAKE, postC("/session/:sessionId/appium/device/shake")) + .put(COMPLEX_FIND, + postC("/session/:sessionId/appium/app/complex_find")) + .put(OPEN_NOTIFICATIONS, + postC("/session/:sessionId/appium/device/open_notifications")) + .put(GET_NETWORK_CONNECTION, + getC("/session/:sessionId/network_connection")) + .put(SET_NETWORK_CONNECTION, + postC("/session/:sessionId/network_connection")) + .put(GET_SETTINGS, getC("/session/:sessionId/appium/settings")) + .put(SET_SETTINGS, postC("/session/:sessionId/appium/settings")) + .put(START_ACTIVITY, + postC("/session/:sessionId/appium/device/start_activity")); + ImmutableMap mobileCommands = builder.build(); + + HttpCommandExecutor mobileExecutor = new HttpCommandExecutor( + mobileCommands, remoteAddress); + super.setCommandExecutor(mobileExecutor); + + super.setErrorHandler(errorHandler); + } + + @Override + public Response execute(String driverCommand, Map parameters) { + + return super.execute(driverCommand, parameters); + } + + @Override + protected Response execute(String command) { + return execute(command, ImmutableMap. of()); + } + + @Override + public ExecuteMethod getExecuteMethod() { + return executeMethod; + } + + /** + * @see InteractsWithApps#resetApp() + */ + @Override + public void resetApp() { + execute(MobileCommand.RESET); + } + + /** + * @see InteractsWithApps#isAppInstalled(String) + */ + @Override + public boolean isAppInstalled(String bundleId) { + Response response = execute(IS_APP_INSTALLED, + ImmutableMap.of("bundleId", bundleId)); + + return Boolean.parseBoolean(response.getValue().toString()); + } + + /** + * @see InteractsWithApps#installApp(String) + */ + @Override + public void installApp(String appPath) { + execute(INSTALL_APP, ImmutableMap.of("appPath", appPath)); + } + + /** + * @see InteractsWithApps#removeApp(String) + */ + @Override + public void removeApp(String bundleId) { + execute(REMOVE_APP, ImmutableMap.of("bundleId", bundleId)); + } + + /** + * @see InteractsWithApps#launchApp() + */ + @Override + public void launchApp() { + execute(LAUNCH_APP); + } + + /** + * @see InteractsWithApps#closeApp() + */ + @Override + public void closeApp() { + execute(CLOSE_APP); + } + + /** + * @see InteractsWithApps#runAppInBackground(int) + */ + @Override + public void runAppInBackground(int seconds) { + execute(RUN_APP_IN_BACKGROUND, ImmutableMap.of("seconds", seconds)); + } + + /** + * Send a key event to the device + * + * @param key + * code for the key pressed on the device + */ + @Override + public void sendKeyEvent(int key) { + execute(KEY_EVENT, getCommandImmutableMap(KEY_CODE, key)); + } + + /** + * @see DeviceActionShortcuts#hideKeyboard() + */ + @Override + public void hideKeyboard() { + execute(HIDE_KEYBOARD); + } + + /** + * @see InteractsWithFiles#pullFile(String) + */ + @Override + public byte[] pullFile(String remotePath) { + Response response = execute(PULL_FILE, + ImmutableMap.of(PATH, remotePath)); + String base64String = response.getValue().toString(); + + return DatatypeConverter.parseBase64Binary(base64String); + } + + /** + * @see InteractsWithFiles#pullFolder(String) + */ + @Override + public byte[] pullFolder(String remotePath) { + Response response = execute(PULL_FOLDER, + ImmutableMap.of(PATH, remotePath)); + String base64String = response.getValue().toString(); + + return DatatypeConverter.parseBase64Binary(base64String); + } + + /** + * @see PerformsTouchActions#performTouchAction(TouchAction) + */ + @SuppressWarnings("rawtypes") + @Override + public TouchAction performTouchAction(TouchAction touchAction) { + ImmutableMap parameters = touchAction + .getParameters(); + touchAction.clearParameters(); + execute(PERFORM_TOUCH_ACTION, parameters); + + return touchAction; + } + + /** + * @see PerformsTouchActions#performMultiTouchAction(MultiTouchAction) + */ + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void performMultiTouchAction(MultiTouchAction multiAction) { + ImmutableMap parameters = multiAction + .getParameters(); + execute(PERFORM_MULTI_TOUCH, parameters); + } + + /** + * @see TouchShortcuts#tap(int, WebElement, int) + */ + @Override + public void tap(int fingers, WebElement element, int duration) { + MultiTouchAction multiTouch = new MultiTouchAction(this); + + for (int i = 0; i < fingers; i++) { + multiTouch.add(createTap(element, duration)); + } + + multiTouch.perform(); + } + + /** + * @see TouchShortcuts#tap(int, int, int, int) + */ + @Override + public void tap(int fingers, int x, int y, int duration) { + MultiTouchAction multiTouch = new MultiTouchAction(this); + + for (int i = 0; i < fingers; i++) { + multiTouch.add(createTap(x, y, duration)); + } + + multiTouch.perform(); + } + + /** + * @see TouchShortcuts#swipe(int, int, int, int, int) + */ + @Override + public void swipe(int startx, int starty, int endx, int endy, int duration) { + TouchAction touchAction = new TouchAction(this); + + // appium converts press-wait-moveto-release to a swipe action + touchAction.press(startx, starty).waitAction(duration) + .moveTo(endx, endy).release(); + + touchAction.perform(); + } /** * Convenience method for pinching an element on the screen. @@ -424,14 +394,14 @@ public void pinch(WebElement el) { */ public void pinch(int x, int y) { MultiTouchAction multiTouch = new MultiTouchAction(this); - + int scrHeight = manage().window().getSize().getHeight(); int yOffset = 100; - + if (y - 100 < 0) { - yOffset = y; + yOffset = y; } else if (y + 100 > scrHeight) { - yOffset = scrHeight - y; + yOffset = scrHeight - y; } TouchAction action0 = new TouchAction(this).press(x, y - yOffset).moveTo(x, y).release(); @@ -481,14 +451,14 @@ public void zoom(WebElement el) { */ public void zoom(int x, int y) { MultiTouchAction multiTouch = new MultiTouchAction(this); - + int scrHeight = manage().window().getSize().getHeight(); int yOffset = 100; - + if (y - 100 < 0) { - yOffset = y; + yOffset = y; } else if (y + 100 > scrHeight) { - yOffset = scrHeight - y; + yOffset = scrHeight - y; } TouchAction action0 = new TouchAction(this).press(x, y).moveTo(x, y - yOffset).release(); @@ -499,302 +469,175 @@ public void zoom(int x, int y) { multiTouch.perform(); } - /** - * In iOS apps, named TextFields have the same accessibility Id as their containing TableElement. - * This is a convenience method for getting the named TextField, rather than its containing element. - * @param name accessiblity id of TextField - * @return The textfield with the given accessibility id - */ - public WebElement getNamedTextField(String name) { - MobileElement element = (MobileElement) findElementByAccessibilityId(name); - if (element.getTagName() != "TextField") { - return element.findElementByAccessibilityId(name); - } - - return element; - } - - /** - * Checks if an app is installed on the device - * @param bundleId bundleId of the app - * @return True if app is installed, false otherwise - */ - public boolean isAppInstalled(String bundleId) { - Response response = execute(IS_APP_INSTALLED, ImmutableMap.of("bundleId", bundleId)); - - return Boolean.parseBoolean(response.getValue().toString()); - } - - /** - * Install an app on the mobile device - * @param appPath path to app to install - */ - public void installApp(String appPath) { - execute(INSTALL_APP, ImmutableMap.of("appPath", appPath)); - } - - /** - * Remove the specified app from the device (uninstall) - * @param bundleId the bunble identifier (or app id) of the app to remove - */ - public void removeApp(String bundleId) { - execute(REMOVE_APP, ImmutableMap.of("bundleId", bundleId)); - } - - /** - * Launch the app which was provided in the capabilities at session creation - */ - public void launchApp() { - execute(LAUNCH_APP); - } - - /** - * Close the app which was provided in the capabilities at session creation - */ - public void closeApp() { - execute(CLOSE_APP); - } - - /** - * Get test-coverage data - * Android-only method - * @param intent intent to broadcast - * @param path path to .ec file - */ - public void endTestCoverage(String intent, String path) { - ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.put("intent", intent).put("path", path); - execute(END_TEST_COVERAGE, builder.build()); - } - - /** - * Lock the device (bring it to the lock screen) for a given number of seconds - * @param seconds number of seconds to lock the screen for - */ - public void lockScreen(int seconds) { - execute(LOCK, ImmutableMap.of("seconds", seconds)); - } - - /** - * Check if the device is locked. - * *Android Only Method* - * - * @return true if device is locked. False otherwise - */ - public boolean isLocked() { - - Response response = execute(IS_LOCKED); - - return Boolean.parseBoolean(response.getValue().toString()); - } - - /** - * Simulate shaking the device - * This is an iOS-only method - */ - public void shake() { - execute(SHAKE); - } - - /** - * Get the current network settings of the device. - * This is an Android-only method - * - * @return NetworkConnectionSetting objects will let you inspect the status of AirplaneMode, Wifi, Data connections - */ - public NetworkConnectionSetting getNetworkConnection() { - Response response = execute(GET_NETWORK_CONNECTION); - - return new NetworkConnectionSetting(Integer.parseInt(response.getValue().toString())); - } - - /** - * Set the network connection of the device. - * This is an Android-only method - * - * @param connection The NetworkConnectionSetting configuration to use for the device - */ - public void setNetworkConnection(NetworkConnectionSetting connection) { - // the new version of the webdriver protocol is going forward with sending JSON message which look like - // {name: "name of endpoint", parameters: "JSON parameters"} - // this is for webdrivers which run on protocols besides HTTP (like TCP) - // we're implementing that pattern here, for this new method, but haven't translated it to all other commands yet - ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.put("name", "network_connection") - .put("parameters", ImmutableMap.of("type", connection.value)); - - execute(SET_NETWORK_CONNECTION, builder.build()); - } - - /** - * Get settings stored for this test session - * It's probably better to use a convenience function, rather than use this function directly. - * Try finding the method for the specific setting you want to read - * - * @return JsonObject, a straight-up hash of settings - */ - public JsonObject getSettings() { - Response response = execute(GET_SETTINGS); - - JsonParser parser = new JsonParser(); - JsonObject settings = (JsonObject)parser.parse(response.getValue().toString()); - - return settings; - } - - /** - * Set settings for this test session - * It's probably better to use a convenience function, rather than use this function directly. - * Try finding the method for the specific setting you want to change - * - * @param settings Map of setting keys and values - */ - private void setSettings(ImmutableMap settings) { - - ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.put("settings", settings); - - execute(SET_SETTINGS, builder.build()); - } - - /** - * Set a setting for this test session - * It's probably better to use a convenience function, rather than use this function directly. - * Try finding the method for the specific setting you want to change - * - * @param setting AppiumSetting you wish to set - * @param value value of the setting - */ - private void setSetting(AppiumSetting setting, Object value) { - ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.put(setting.toString(), value); - setSettings(builder.build()); - } - - /** - * Set the `ignoreUnimportantViews` setting. - * *Android-only method* - * - * Sets whether Android devices should use `setCompressedLayoutHeirarchy()` which ignores all views which are marked IMPORTANT_FOR_ACCESSIBILITY_NO or IMPORTANT_FOR_ACCESSIBILITY_AUTO (and have been deemed not important by the system), in an attempt to make things less confusing or faster. - * - * @param compress ignores unimportant views if true, doesn't ignore otherwise. - */ - public void ignoreUnimportantViews(Boolean compress) { - setSetting(AppiumSetting.IGNORE_UNIMPORTANT_VIEWS, compress); - } - - @Override - public WebDriver context(String name) { - if (!_isNotNullOrEmpty(name)) { - throw new IllegalArgumentException("Must supply a context name"); - } - - execute(DriverCommand.SWITCH_TO_CONTEXT, ImmutableMap.of("name", name)); - return AppiumDriver.this; - } + /** + * Get settings stored for this test session It's probably better to use a + * convenience function, rather than use this function directly. Try finding + * the method for the specific setting you want to read + * + * @return JsonObject, a straight-up hash of settings + */ + public JsonObject getSettings() { + Response response = execute(GET_SETTINGS); + + JsonParser parser = new JsonParser(); + JsonObject settings = (JsonObject) parser.parse(response.getValue() + .toString()); + + return settings; + } + + /** + * Set settings for this test session It's probably better to use a + * convenience function, rather than use this function directly. Try finding + * the method for the specific setting you want to change + * + * @param settings + * Map of setting keys and values + */ + private void setSettings(ImmutableMap settings) { + execute(SET_SETTINGS, getCommandImmutableMap(SETTINGS, settings)); + } + + /** + * Set a setting for this test session It's probably better to use a + * convenience function, rather than use this function directly. Try finding + * the method for the specific setting you want to change + * + * @param setting + * AppiumSetting you wish to set + * @param value + * value of the setting + */ + protected void setSetting(AppiumSetting setting, Object value) { + setSettings(getCommandImmutableMap(setting.toString(), value)); + } + + /** + * Lock the device (bring it to the lock screen) for a given number of + * seconds + * + * @param seconds + * number of seconds to lock the screen for + */ + public void lockScreen(int seconds) { + execute(LOCK, ImmutableMap.of("seconds", seconds)); + } @Override - public Set getContextHandles() { - Response response = execute(DriverCommand.GET_CONTEXT_HANDLES); - Object value = response.getValue(); - try { - List returnedValues = (List)value; - return new LinkedHashSet(returnedValues); - } catch (ClassCastException ex) { - throw new WebDriverException("Returned value cannot be converted to List: " + value, ex); - } - } - - @Override - public String getContext() { - String contextName = String.valueOf(execute(DriverCommand.GET_CURRENT_CONTEXT_HANDLE).getValue()); - if (contextName.equals("null")) { - return null; - } - return contextName; - } - - @Override - public void rotate(ScreenOrientation orientation) { - execute(DriverCommand.SET_SCREEN_ORIENTATION, ImmutableMap.of("orientation", orientation.value().toUpperCase())); - } - - @Override - public ScreenOrientation getOrientation() { - Response response = execute(DriverCommand.GET_SCREEN_ORIENTATION); - String orientation = response.getValue().toString().toLowerCase(); - if (orientation.equals(ScreenOrientation.LANDSCAPE.value())) { - return ScreenOrientation.LANDSCAPE; - } else if (orientation.equals(ScreenOrientation.PORTRAIT.value())) { - return ScreenOrientation.PORTRAIT; - } else { - throw new WebDriverException("Unexpected orientation returned: " + orientation); - } - } - - @Override - public WebElement findElementByIosUIAutomation(String using) { - return findElement("-ios uiautomation", using); - } - - @Override - public List findElementsByIosUIAutomation(String using) { - return findElements("-ios uiautomation", using); - } - - @Override - public WebElement findElementByAndroidUIAutomator(String using) { - return findElement("-android uiautomator", using); - } - - @Override - public List findElementsByAndroidUIAutomator(String using) { - return findElements("-android uiautomator", using); - } - - @Override - public WebElement findElementByAccessibilityId(String using) { - return findElement("accessibility id", using); - } - - @Override - public List findElementsByAccessibilityId(String using) { - return findElements("accessibility id", using); - } - - @Override - public Location location() { - return locationContext.location(); - } - - @Override - public void setLocation(Location location) { - locationContext.setLocation(location); - } - - private TouchAction createTap(WebElement element, int duration) { - TouchAction tap = new TouchAction(this); - return tap.press(element).waitAction(duration).release(); - } - - private TouchAction createTap(int x, int y, int duration) { - TouchAction tap = new TouchAction(this); - return tap.press(x, y).waitAction(duration).release(); - } - - private static CommandInfo getC(String url) { - return new CommandInfo(url, HttpMethod.GET); - } - - private static CommandInfo postC(String url) { - return new CommandInfo(url, HttpMethod.POST); - } - - private static CommandInfo deleteC(String url) { - return new CommandInfo(url, HttpMethod.DELETE); - } - - public URL getRemoteAddress() { - return remoteAddress; - } + public WebDriver context(String name) { + if (!_isNotNullOrEmpty(name)) { + throw new IllegalArgumentException("Must supply a context name"); + } + + execute(DriverCommand.SWITCH_TO_CONTEXT, ImmutableMap.of("name", name)); + return AppiumDriver.this; + } + + @SuppressWarnings("unchecked") + @Override + public Set getContextHandles() { + Response response = execute(DriverCommand.GET_CONTEXT_HANDLES); + Object value = response.getValue(); + try { + List returnedValues = (List) value; + return new LinkedHashSet(returnedValues); + } catch (ClassCastException ex) { + throw new WebDriverException( + "Returned value cannot be converted to List: " + + value, ex); + } + } + + @Override + public String getContext() { + String contextName = String.valueOf(execute( + DriverCommand.GET_CURRENT_CONTEXT_HANDLE).getValue()); + if (contextName.equals("null")) { + return null; + } + return contextName; + } + + @Override + public void rotate(ScreenOrientation orientation) { + execute(DriverCommand.SET_SCREEN_ORIENTATION, ImmutableMap.of( + "orientation", orientation.value().toUpperCase())); + } + + @Override + public ScreenOrientation getOrientation() { + Response response = execute(DriverCommand.GET_SCREEN_ORIENTATION); + String orientation = response.getValue().toString().toLowerCase(); + if (orientation.equals(ScreenOrientation.LANDSCAPE.value())) { + return ScreenOrientation.LANDSCAPE; + } else if (orientation.equals(ScreenOrientation.PORTRAIT.value())) { + return ScreenOrientation.PORTRAIT; + } else { + throw new WebDriverException("Unexpected orientation returned: " + + orientation); + } + } + + @Override + public WebElement findElementByAccessibilityId(String using) { + return findElement("accessibility id", using); + } + + @Override + public List findElementsByAccessibilityId(String using) { + return findElements("accessibility id", using); + } + + @Override + public Location location() { + return locationContext.location(); + } + + @Override + public void setLocation(Location location) { + locationContext.setLocation(location); + } + + private TouchAction createTap(WebElement element, int duration) { + TouchAction tap = new TouchAction(this); + return tap.press(element).waitAction(duration).release(); + } + + private TouchAction createTap(int x, int y, int duration) { + TouchAction tap = new TouchAction(this); + return tap.press(x, y).waitAction(duration).release(); + } + + private static CommandInfo getC(String url) { + return new CommandInfo(url, HttpMethod.GET); + } + + private static CommandInfo postC(String url) { + return new CommandInfo(url, HttpMethod.POST); + } + + @SuppressWarnings("unused") + private static CommandInfo deleteC(String url) { + return new CommandInfo(url, HttpMethod.DELETE); + } + + public URL getRemoteAddress() { + return remoteAddress; + } + + /** + * Checks if a string is null, empty, or whitespace. + * + * @param str + * String to check. + * + * @return True if str is not null or empty. + */ + protected static boolean _isNotNullOrEmpty(String str) { + return str != null && !str.isEmpty() && str.trim().length() > 0; + } + + protected static boolean _isNotNullOrEmpty(Object ob) { + return ob != null; + } } \ No newline at end of file diff --git a/src/main/java/io/appium/java_client/DeviceActionShortcuts.java b/src/main/java/io/appium/java_client/DeviceActionShortcuts.java new file mode 100644 index 000000000..1a4d71667 --- /dev/null +++ b/src/main/java/io/appium/java_client/DeviceActionShortcuts.java @@ -0,0 +1,23 @@ +package io.appium.java_client; + + +public interface DeviceActionShortcuts { + + /** + * Hides the keyboard if it is showing. + * On iOS, there are multiple strategies for hiding the keyboard. Defaults to the "tapOutside" strategy (taps outside the keyboard). + * Switch to using hideKeyboard(HideKeyboardStrategy.PRESS_KEY, "Done") if this doesn't work. + */ + public void hideKeyboard(); + + /** + * Send a key event to the device + * + * @param key code for the key pressed on the device + * + * @see io.appium.java_client.android.AndroidKeyCode + * @see io.appium.java_client.ios.IOSKeyCode + */ + public void sendKeyEvent(int key); + +} diff --git a/src/main/java/io/appium/java_client/InteractsWithApps.java b/src/main/java/io/appium/java_client/InteractsWithApps.java new file mode 100644 index 000000000..59f6d0566 --- /dev/null +++ b/src/main/java/io/appium/java_client/InteractsWithApps.java @@ -0,0 +1,54 @@ +package io.appium.java_client; + +public interface InteractsWithApps { + /** + * Launch the app which was provided in the capabilities at session creation + */ + public void launchApp(); + + /** + * Install an app on the mobile device + * + * @param appPath + * path to app to install + */ + public void installApp(String appPath); + + /** + * Checks if an app is installed on the device + * + * @param bundleId + * bundleId of the app + * @return True if app is installed, false otherwise + */ + public boolean isAppInstalled(String bundleId); + + /** + * Reset the currently running app for this session + */ + public void resetApp(); + + /** + * Runs the current app as a background app for the number of seconds + * requested. This is a synchronous method, it returns after the back has + * been returned to the foreground. + * + * @param seconds + * Number of seconds to run App in background + */ + public void runAppInBackground(int seconds); + + /** + * Remove the specified app from the device (uninstall) + * + * @param bundleId + * the bunble identifier (or app id) of the app to remove + */ + public void removeApp(String bundleId); + + /** + * Close the app which was provided in the capabilities at session creation + */ + public void closeApp(); + +} diff --git a/src/main/java/io/appium/java_client/InteractsWithFiles.java b/src/main/java/io/appium/java_client/InteractsWithFiles.java new file mode 100644 index 000000000..d84f0a9ab --- /dev/null +++ b/src/main/java/io/appium/java_client/InteractsWithFiles.java @@ -0,0 +1,30 @@ +package io.appium.java_client; + +public interface InteractsWithFiles { + + /** + * + * @param remotePath + * On Android and iOS, this is either the path to the file + * (relative to the root of the app's file system). On iOS only, + * if path starts with /AppName.app, which will be replaced with + * the application's .app directory + * @return A byte array of Base64 encoded data. + */ + public byte[] pullFile(String remotePath); + + /** + * Pull a folder from the simulator/device. Does not work on iOS Real + * Devices, but works on simulators + * + * @param remotePath + * On Android and iOS, this is either the path to the file + * (relative to the root of the app's file system). On iOS only, + * if path starts with /AppName.app, which will be replaced with + * the application's .app directory + * @return A byte array of Base64 encoded data, representing a ZIP ARCHIVE + * of the contents of the requested folder. + */ + public byte[] pullFolder(String remotePath); + +} diff --git a/src/main/java/io/appium/java_client/MobileDriver.java b/src/main/java/io/appium/java_client/MobileDriver.java index f876b59ac..9523175a6 100644 --- a/src/main/java/io/appium/java_client/MobileDriver.java +++ b/src/main/java/io/appium/java_client/MobileDriver.java @@ -17,19 +17,13 @@ package io.appium.java_client; - import org.openqa.selenium.ContextAware; import org.openqa.selenium.WebDriver; import org.openqa.selenium.remote.Response; import java.util.Map; -public interface MobileDriver extends WebDriver, ContextAware { - - - public Response execute(String driverCommand, Map parameters); - - public TouchAction performTouchAction(TouchAction touchAction); - - public void performMultiTouchAction(MultiTouchAction multiAction); +public interface MobileDriver extends WebDriver, ContextAware, + PerformsTouchActions { + public Response execute(String driverCommand, Map parameters); } diff --git a/src/main/java/io/appium/java_client/MobileElement.java b/src/main/java/io/appium/java_client/MobileElement.java index 4a4e0c51e..eda77940f 100644 --- a/src/main/java/io/appium/java_client/MobileElement.java +++ b/src/main/java/io/appium/java_client/MobileElement.java @@ -17,8 +17,7 @@ package io.appium.java_client; -import java.util.List; - +import com.google.common.collect.ImmutableMap; import org.openqa.selenium.By; import org.openqa.selenium.Dimension; import org.openqa.selenium.Point; @@ -26,10 +25,9 @@ import org.openqa.selenium.remote.FileDetector; import org.openqa.selenium.remote.RemoteWebElement; -import com.google.common.collect.ImmutableMap; +import java.util.List; -public class MobileElement extends RemoteWebElement implements FindsByAccessibilityId, FindsByAndroidUIAutomator, - FindsByIosUIAutomation { +public abstract class MobileElement extends RemoteWebElement implements FindsByAccessibilityId { protected FileDetector fileDetector; @@ -41,22 +39,6 @@ public WebElement findElement(By by) { return by.findElement(this); } - public WebElement findElementByIosUIAutomation(String using) { - return findElement("-ios uiautomation", using); - } - - public List findElementsByIosUIAutomation(String using) { - return findElements("-ios uiautomation", using); - } - - public WebElement findElementByAndroidUIAutomator(String using) { - return findElement("-android uiautomator", using); - } - - public List findElementsByAndroidUIAutomator(String using) { - return findElements("-android uiautomator", using); - } - public WebElement findElementByAccessibilityId(String using) { return findElement("accessibility id", using); } diff --git a/src/main/java/io/appium/java_client/PerformsTouchActions.java b/src/main/java/io/appium/java_client/PerformsTouchActions.java new file mode 100644 index 000000000..f32323dba --- /dev/null +++ b/src/main/java/io/appium/java_client/PerformsTouchActions.java @@ -0,0 +1,31 @@ +package io.appium.java_client; + +public interface PerformsTouchActions { + /** + * Performs a chain of touch actions, which together can be considered an + * entire gesture. See the Webriver 3 spec + * https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html + * + * It's more convenient to call the perform() method of the TouchAction + * object itself. + * + * @param touchAction + * A TouchAction object, which contains a list of individual + * touch actions to perform + * @return the same touch action object + */ + public TouchAction performTouchAction(TouchAction touchAction); + + /** + * Performs multiple TouchAction gestures at the same time, to simulate + * multiple fingers/touch inputs. See the Webriver 3 spec + * https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html + * + * It's more convenient to call the perform() method of the MultiTouchAction + * object. + * + * @param multiAction + * the MultiTouchAction object to perform. + */ + public void performMultiTouchAction(MultiTouchAction multiAction); +} diff --git a/src/main/java/io/appium/java_client/ScrollsTo.java b/src/main/java/io/appium/java_client/ScrollsTo.java new file mode 100644 index 000000000..348ab4a40 --- /dev/null +++ b/src/main/java/io/appium/java_client/ScrollsTo.java @@ -0,0 +1,17 @@ +package io.appium.java_client; + +public interface ScrollsTo { + + /** + * Scroll to an element which contains the given text. + * @param text + */ + public MobileElement scrollTo(String text); + + /** + * Scroll to an element with the given text. + * @param text + */ + public MobileElement scrollToExact(String text); + +} diff --git a/src/main/java/io/appium/java_client/TouchShortcuts.java b/src/main/java/io/appium/java_client/TouchShortcuts.java new file mode 100644 index 000000000..22e914da4 --- /dev/null +++ b/src/main/java/io/appium/java_client/TouchShortcuts.java @@ -0,0 +1,99 @@ +package io.appium.java_client; + +import org.openqa.selenium.WebElement; + +public interface TouchShortcuts { + + /** + * Convenience method for "zooming in" on an element on the screen. + * "zooming in" refers to the action of two appendages pressing the screen and sliding away from each other. + * NOTE: + * This convenience method slides touches away from the element, if this would happen to place one of them + * off the screen, appium will return an outOfBounds error. In this case, revert to using the MultiTouchAction api + * instead of this method. + * + * @param x x coordinate to start zoom on + * @param y y coordinate to start zoom on + */ + public void zoom(int x, int y); + + /** + * Convenience method for "zooming in" on an element on the screen. + * "zooming in" refers to the action of two appendages pressing the screen and sliding away from each other. + * NOTE: + * This convenience method slides touches away from the element, if this would happen to place one of them + * off the screen, appium will return an outOfBounds error. In this case, revert to using the MultiTouchAction api + * instead of this method. + * + * @param el The element to pinch + */ + public void zoom(WebElement el); + + /** + * Convenience method for tapping a position on the screen + * + * @param fingers + * number of fingers/appendages to tap with + * @param x + * x coordinate + * @param y + * y coordinate + * @param duration + */ + public void tap(int fingers, int x, int y, int duration); + + /** + * Convenience method for tapping the center of an element on the screen + * + * @param fingers + * number of fingers/appendages to tap with + * @param element + * element to tap + * @param duration + * how long between pressing down, and lifting fingers/appendages + */ + public void tap(int fingers, WebElement element, int duration); + + /** + * Convenience method for swiping across the screen + * + * @param startx + * starting x coordinate + * @param starty + * starting y coordinate + * @param endx + * ending x coordinate + * @param endy + * ending y coordinate + * @param duration + * amount of time in milliseconds for the entire swipe action to + * take + */ + public void swipe(int startx, int starty, int endx, int endy, int duration); + + /** + * Convenience method for pinching an element on the screen. + * "pinching" refers to the action of two appendages pressing the screen and sliding towards each other. + * NOTE: + * This convenience method places the initial touches around the element at a distance, if this would happen to place + * one of them off the screen, appium will return an outOfBounds error. In this case, revert to using the + * MultiTouchAction api instead of this method. + * + * @param x x coordinate to terminate the pinch on + * @param y y coordinate to terminate the pinch on + */ + public void pinch(int x, int y); + + /** + * Convenience method for pinching an element on the screen. + * "pinching" refers to the action of two appendages pressing the screen and sliding towards each other. + * NOTE: + * This convenience method places the initial touches around the element, if this would happen to place one of them + * off the screen, appium with return an outOfBounds error. In this case, revert to using the MultiTouchAction api + * instead of this method. + * + * @param el The element to pinch + */ + public void pinch(WebElement el); + +} diff --git a/src/main/java/io/appium/java_client/android/AndroidDeviceActionShortcuts.java b/src/main/java/io/appium/java_client/android/AndroidDeviceActionShortcuts.java new file mode 100644 index 000000000..545b23698 --- /dev/null +++ b/src/main/java/io/appium/java_client/android/AndroidDeviceActionShortcuts.java @@ -0,0 +1,17 @@ +package io.appium.java_client.android; + +import io.appium.java_client.DeviceActionShortcuts; + +public interface AndroidDeviceActionShortcuts extends DeviceActionShortcuts { + /** + * Send a key event along with an Android metastate to an Android device + * Metastates are things like *shift* to get uppercase characters + * + * @param key code for the key pressed on the Android device + * @param metastate metastate for the keypress + * + * @see AndroidKeyCode + * @see AndroidKeyMetastate + */ + public void sendKeyEvent(int key, Integer metastate); +} diff --git a/src/main/java/io/appium/java_client/android/AndroidDriver.java b/src/main/java/io/appium/java_client/android/AndroidDriver.java new file mode 100644 index 000000000..28dbd295f --- /dev/null +++ b/src/main/java/io/appium/java_client/android/AndroidDriver.java @@ -0,0 +1,272 @@ +package io.appium.java_client.android; + +import com.google.common.collect.ImmutableMap; + +import io.appium.java_client.AppiumDriver; +import io.appium.java_client.AppiumSetting; +import io.appium.java_client.FindsByAndroidUIAutomator; +import io.appium.java_client.MobileElement; +import io.appium.java_client.NetworkConnectionSetting; +import io.appium.java_client.android.internal.JsonToAndroidElementConverter; +import io.appium.java_client.remote.MobilePlatform; + +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.Response; + +import java.net.URL; +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; +import static io.appium.java_client.MobileCommand.*; +import static io.appium.java_client.remote.MobileCapabilityType.*; + +public class AndroidDriver extends AppiumDriver implements + AndroidDeviceActionShortcuts, HasAppStrings, HasNetworkConnection, PushesFiles, + StartsActivity, FindsByAndroidUIAutomator { + + private static final String ANDROID_PLATFORM = MobilePlatform.ANDROID; + + private final String METASTATE_PARAM = "metastate"; + private final String LANGUAGE_PARAM = "language"; + private final String CONNECTION_NAME_PARAM = "name"; + private final String CONNECTION_PARAM_PARAM = "parameters"; + private final String DATA_PARAM = "data"; + private final String INTENT_PARAM = "intent"; + + private final String CONNECTION_NAME_VALUE = "network_connection"; + + public AndroidDriver(URL remoteAddress, Capabilities desiredCapabilities) { + super(remoteAddress, substituteMobilePlatform(desiredCapabilities, + ANDROID_PLATFORM)); + this.setElementConverter(new JsonToAndroidElementConverter(this)); + } + + /** + * Scroll forward to the element which has a description or name which contains the input text. + * The scrolling is performed on the first scrollView present on the UI + * @param text + */ + @Override + public MobileElement scrollTo(String text) { + String uiScrollables = UiScrollable("new UiSelector().descriptionContains(\"" + text + "\")") + + UiScrollable("new UiSelector().textContains(\"" + text + "\")"); + return (MobileElement) findElementByAndroidUIAutomator(uiScrollables); + } + + /** + * Scroll forward to the element which has a description or name which exactly matches the input text. + * The scrolling is performed on the first scrollView present on the UI + * @param text + */ + @Override + public MobileElement scrollToExact(String text) { + String uiScrollables = UiScrollable("new UiSelector().description(\"" + text + "\")") + + UiScrollable("new UiSelector().text(\"" + text + "\")"); + return (MobileElement) findElementByAndroidUIAutomator(uiScrollables); + } + + static String UiScrollable(String uiSelector) { + return "new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(" + uiSelector + ".instance(0));"; + } + + /** + * @param key + * code for the key pressed on the Android device + * @param metastate + * metastate for the keypress + * + * @see AndroidKeyCode + * @see AndroidKeyMetastate + * @see AndroidDeviceActionShortcuts#sendKeyEvent(int, Integer) + */ + @Override + public void sendKeyEvent(int key, Integer metastate) { + String[] parameters = new String[] { KEY_CODE, METASTATE_PARAM }; + Object[] values = new Object[] { key, metastate }; + execute(KEY_EVENT, getCommandImmutableMap(parameters, values)); + } + + /** + * @see HasAppStrings#getAppStrings() + */ + @Override + public String getAppStrings() { + Response response = execute(GET_STRINGS); + return response.getValue().toString(); + } + + /** + * @param language + * strings language code + * @return a string of all the localized strings defined in the app + * + * @see HasAppStrings#getAppStrings(String) + */ + @Override + public String getAppStrings(String language) { + Response response = execute(GET_STRINGS, + getCommandImmutableMap(LANGUAGE_PARAM, language)); + return response.getValue().toString(); + } + + /** + * @see HasNetworkConnection#getNetworkConnection() + */ + @Override + public NetworkConnectionSetting getNetworkConnection() { + Response response = execute(GET_NETWORK_CONNECTION); + return new NetworkConnectionSetting(Integer.parseInt(response + .getValue().toString())); + } + + /** + * @param connection + * The NetworkConnectionSetting configuration to use for the + * device + * + * @see HasNetworkConnection#setNetworkConnection(NetworkConnectionSetting) + */ + @Override + public void setNetworkConnection(NetworkConnectionSetting connection) { + // the new version of the webdriver protocol is going forward with + // sending JSON message which look like + // {name: "name of endpoint", parameters: "JSON parameters"} + // this is for webdrivers which run on protocols besides HTTP (like TCP) + // we're implementing that pattern here, for this new method, but + // haven't translated it to all other commands yet + String[] parameters = new String[] { CONNECTION_NAME_PARAM, + CONNECTION_PARAM_PARAM }; + Object[] values = new Object[] { CONNECTION_NAME_VALUE, + ImmutableMap.of("type", connection.value) }; + execute(SET_NETWORK_CONNECTION, + getCommandImmutableMap(parameters, values)); + } + + /** + * @param remotePath + * Path to file to write data to on remote device + * @param base64Data + * Base64 encoded byte array of data to write to remote device + * @see PushesFiles#pushFile(String, byte[]) + */ + @Override + public void pushFile(String remotePath, byte[] base64Data) { + String[] parameters = new String[] { PATH, DATA_PARAM }; + Object[] values = new Object[] { remotePath, base64Data }; + execute(PUSH_FILE, getCommandImmutableMap(parameters, values)); + } + + /** + * @param appPackage + * The package containing the activity. [Required] + * @param appActivity + * The activity to start. [Required] + * @param appWaitPackage + * Automation will begin after this package starts. [Optional] + * @param appWaitActivity + * Automation will begin after this activity starts. [Optional] + * @example driver.startActivity("com.foo.bar", ".MyActivity", null, null); + * + * @see StartsActivity#startActivity(String, String, String, String) + */ + public void startActivity(String appPackage, String appActivity, + String appWaitPackage, String appWaitActivity) + throws IllegalArgumentException { + + checkArgument( + (_isNotNullOrEmpty(appPackage) && _isNotNullOrEmpty(appActivity)), + String.format("'%s' and '%s' are required.", APP_PACKAGE, + APP_ACTIVITY)); + + appWaitPackage = _isNotNullOrEmpty(appWaitPackage) ? appWaitPackage + : ""; + appWaitActivity = _isNotNullOrEmpty(appWaitActivity) ? appWaitActivity + : ""; + + ImmutableMap parameters = ImmutableMap.of(APP_PACKAGE, + appPackage, APP_ACTIVITY, appActivity, APP_WAIT_PACKAGE, + appWaitPackage, APP_WAIT_ACTIVITY, appWaitActivity); + + execute(START_ACTIVITY, parameters); + } + + /** + * @param appPackage The package containing the activity. [Required] + * @param appActivity The activity to start. [Required] + * @example + * *.startActivity("com.foo.bar", ".MyActivity"); + * @see StartsActivity#startActivity(String, String) + */ + @Override + public void startActivity(String appPackage, String appActivity) + throws IllegalArgumentException { + this.startActivity(appPackage, appActivity, null, null); + } + + /** + * Get test-coverage data + * + * @param intent + * intent to broadcast + * @param path + * path to .ec file + */ + public void endTestCoverage(String intent, String path) { + String[] parameters = new String[] { INTENT_PARAM, PATH }; + Object[] values = new Object[] { intent, path }; + execute(END_TEST_COVERAGE, getCommandImmutableMap(parameters, values)); + } + + /** + * Get the current activity being run on the mobile device + */ + public String currentActivity() { + Response response = execute(CURRENT_ACTIVITY); + return response.getValue().toString(); + } + + /** + * Open the notification shade, on Android devices. + */ + public void openNotifications() { + execute(OPEN_NOTIFICATIONS); + } + + /** + * Check if the device is locked. + * + * @return true if device is locked. False otherwise + */ + public boolean isLocked() { + Response response = execute(IS_LOCKED); + return Boolean.parseBoolean(response.getValue().toString()); + } + + /** + * Set the `ignoreUnimportantViews` setting. *Android-only method* + * + * Sets whether Android devices should use `setCompressedLayoutHeirarchy()` + * which ignores all views which are marked IMPORTANT_FOR_ACCESSIBILITY_NO + * or IMPORTANT_FOR_ACCESSIBILITY_AUTO (and have been deemed not important + * by the system), in an attempt to make things less confusing or faster. + * + * @param compress + * ignores unimportant views if true, doesn't ignore otherwise. + */ + // Should be moved to the subclass + public void ignoreUnimportantViews(Boolean compress) { + setSetting(AppiumSetting.IGNORE_UNIMPORTANT_VIEWS, compress); + } + + @Override + public WebElement findElementByAndroidUIAutomator(String using) { + return findElement("-android uiautomator", using); + } + + @Override + public List findElementsByAndroidUIAutomator(String using) { + return findElements("-android uiautomator", using); + } + +} diff --git a/src/main/java/io/appium/java_client/android/AndroidElement.java b/src/main/java/io/appium/java_client/android/AndroidElement.java new file mode 100644 index 000000000..cb71e1014 --- /dev/null +++ b/src/main/java/io/appium/java_client/android/AndroidElement.java @@ -0,0 +1,23 @@ +package io.appium.java_client.android; + +import java.util.List; + +import org.openqa.selenium.WebElement; + +import io.appium.java_client.FindsByAndroidUIAutomator; +import io.appium.java_client.MobileElement; + +public class AndroidElement extends MobileElement implements + FindsByAndroidUIAutomator { + + @Override + public WebElement findElementByAndroidUIAutomator(String using) { + return findElement("-android uiautomator", using); + } + + @Override + public List findElementsByAndroidUIAutomator(String using) { + return findElements("-android uiautomator", using); + } + +} diff --git a/src/main/java/io/appium/java_client/AndroidKeyCode.java b/src/main/java/io/appium/java_client/android/AndroidKeyCode.java similarity index 95% rename from src/main/java/io/appium/java_client/AndroidKeyCode.java rename to src/main/java/io/appium/java_client/android/AndroidKeyCode.java index a72721d1c..355325b51 100644 --- a/src/main/java/io/appium/java_client/AndroidKeyCode.java +++ b/src/main/java/io/appium/java_client/android/AndroidKeyCode.java @@ -15,7 +15,7 @@ +limitations under the License. + */ -package io.appium.java_client; +package io.appium.java_client.android; /** * Some common key codes for Android Key Events diff --git a/src/main/java/io/appium/java_client/AndroidKeyMetastate.java b/src/main/java/io/appium/java_client/android/AndroidKeyMetastate.java similarity index 96% rename from src/main/java/io/appium/java_client/AndroidKeyMetastate.java rename to src/main/java/io/appium/java_client/android/AndroidKeyMetastate.java index 0bf56ad75..cd2d50277 100644 --- a/src/main/java/io/appium/java_client/AndroidKeyMetastate.java +++ b/src/main/java/io/appium/java_client/android/AndroidKeyMetastate.java @@ -15,7 +15,7 @@ +limitations under the License. + */ -package io.appium.java_client; +package io.appium.java_client.android; /** * Metastates for Android Key Events diff --git a/src/main/java/io/appium/java_client/android/HasAppStrings.java b/src/main/java/io/appium/java_client/android/HasAppStrings.java new file mode 100644 index 000000000..a4f619322 --- /dev/null +++ b/src/main/java/io/appium/java_client/android/HasAppStrings.java @@ -0,0 +1,21 @@ +package io.appium.java_client.android; + + +public interface HasAppStrings { + + /** + * Get all defined Strings from an Android app for the default language + * + * @return a string of all the localized strings defined in the app + */ + public String getAppStrings(); + + /** + * Get all defined Strings from an Android app for the specified language + * + * @param language strings language code + * @return a string of all the localized strings defined in the app + */ + public String getAppStrings(String language); + +} diff --git a/src/main/java/io/appium/java_client/android/HasNetworkConnection.java b/src/main/java/io/appium/java_client/android/HasNetworkConnection.java new file mode 100644 index 000000000..51d4c5c99 --- /dev/null +++ b/src/main/java/io/appium/java_client/android/HasNetworkConnection.java @@ -0,0 +1,24 @@ +package io.appium.java_client.android; + +import io.appium.java_client.NetworkConnectionSetting; + +public interface HasNetworkConnection { + + /** + * Get the current network settings of the device. + * + * @return NetworkConnectionSetting objects will let you inspect the status + * of AirplaneMode, Wifi, Data connections + */ + public NetworkConnectionSetting getNetworkConnection(); + + /** + * Set the network connection of the device. This is an Android-only method + * + * @param connection + * The NetworkConnectionSetting configuration to use for the + * device + */ + public void setNetworkConnection(NetworkConnectionSetting connection); + +} diff --git a/src/main/java/io/appium/java_client/android/PushesFiles.java b/src/main/java/io/appium/java_client/android/PushesFiles.java new file mode 100644 index 000000000..8c1d81c86 --- /dev/null +++ b/src/main/java/io/appium/java_client/android/PushesFiles.java @@ -0,0 +1,17 @@ +package io.appium.java_client.android; + +import io.appium.java_client.InteractsWithFiles; + +public interface PushesFiles extends InteractsWithFiles { + + /** + * Save base64 encoded data as a file on the remote mobile device. + * + * @param remotePath + * Path to file to write data to on remote device + * @param base64Data + * Base64 encoded byte array of data to write to remote device + */ + public void pushFile(String remotePath, byte[] base64Data); + +} diff --git a/src/main/java/io/appium/java_client/android/StartsActivity.java b/src/main/java/io/appium/java_client/android/StartsActivity.java new file mode 100644 index 000000000..3dd9eaf59 --- /dev/null +++ b/src/main/java/io/appium/java_client/android/StartsActivity.java @@ -0,0 +1,30 @@ +package io.appium.java_client.android; + +public interface StartsActivity { + /** + * This method should start arbitrary activity during a test. If the activity belongs to + * another application, that application is started and the activity is opened. + * + * @param appPackage The package containing the activity. [Required] + * @param appActivity The activity to start. [Required] + * @param appWaitPackage Automation will begin after this package starts. [Optional] + * @param appWaitActivity Automation will begin after this activity starts. [Optional] + * @example + * *.startActivity("com.foo.bar", ".MyActivity", null, null); + */ + public void startActivity(String appPackage, String appActivity, String appWaitPackage, String appWaitActivity) + throws IllegalArgumentException; + + /** + * This method should start arbitrary activity during a test. If the activity belongs to + * another application, that application is started and the activity is opened. + * + * @param appPackage The package containing the activity. [Required] + * @param appActivity The activity to start. [Required] + * @example + * *.startActivity("com.foo.bar", ".MyActivity"); + */ + public void startActivity(String appPackage, String appActivity) + throws IllegalArgumentException; + +} diff --git a/src/main/java/io/appium/java_client/android/internal/JsonToAndroidElementConverter.java b/src/main/java/io/appium/java_client/android/internal/JsonToAndroidElementConverter.java new file mode 100644 index 000000000..8e419e9e9 --- /dev/null +++ b/src/main/java/io/appium/java_client/android/internal/JsonToAndroidElementConverter.java @@ -0,0 +1,21 @@ +package io.appium.java_client.android.internal; + +import io.appium.java_client.AppiumDriver; +import io.appium.java_client.MobileElement; +import io.appium.java_client.android.AndroidElement; +import io.appium.java_client.internal.JsonToMobileElementConverter; + +public class JsonToAndroidElementConverter extends JsonToMobileElementConverter { + + public JsonToAndroidElementConverter(AppiumDriver driver) { + super(driver); + } + + @Override + protected MobileElement newMobileElement() { + AndroidElement toReturn = new AndroidElement(); + toReturn.setParent(driver); + return toReturn; + } + +} 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 a7b07a4b9..c74da925d 100644 --- a/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java +++ b/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java @@ -17,8 +17,8 @@ * Reconstitutes {@link WebElement}s from their JSON representation. Will recursively convert Lists * and Maps to catch nested references. All other values pass through the converter unchanged. */ -public class JsonToMobileElementConverter extends JsonToWebElementConverter { - private AppiumDriver driver; +public abstract class JsonToMobileElementConverter extends JsonToWebElementConverter { + protected AppiumDriver driver; public JsonToMobileElementConverter(AppiumDriver driver) { super(driver); @@ -53,9 +53,9 @@ public Object apply(Object result) { return result; } - protected MobileElement newMobileElement() { - MobileElement toReturn = new MobileElement(); - toReturn.setParent(driver); - return toReturn; - } + protected abstract MobileElement newMobileElement(); //{ + //MobileElement toReturn = new MobileElement(); + //toReturn.setParent(driver); + //return toReturn; + //} } diff --git a/src/main/java/io/appium/java_client/ios/GetsNamedTextField.java b/src/main/java/io/appium/java_client/ios/GetsNamedTextField.java new file mode 100644 index 000000000..6898c769c --- /dev/null +++ b/src/main/java/io/appium/java_client/ios/GetsNamedTextField.java @@ -0,0 +1,17 @@ +package io.appium.java_client.ios; + +import org.openqa.selenium.WebElement; + +public interface GetsNamedTextField { + /** + * In iOS apps, named TextFields have the same accessibility Id as their + * containing TableElement. This is a convenience method for getting the + * named TextField, rather than its containing element. + * + * @param name + * accessiblity id of TextField + * @return The textfield with the given accessibility id + */ + public WebElement getNamedTextField(String name); + +} diff --git a/src/main/java/io/appium/java_client/ios/IOSDeviceActionShortcuts.java b/src/main/java/io/appium/java_client/ios/IOSDeviceActionShortcuts.java new file mode 100644 index 000000000..e8f0e2c5f --- /dev/null +++ b/src/main/java/io/appium/java_client/ios/IOSDeviceActionShortcuts.java @@ -0,0 +1,37 @@ +package io.appium.java_client.ios; + +import io.appium.java_client.DeviceActionShortcuts; + +public interface IOSDeviceActionShortcuts extends DeviceActionShortcuts { + + /** + * Hides the keyboard by pressing the button specified by keyName if it is + * showing. + * + * @param keyName + * The button pressed by the mobile driver to attempt hiding the + * keyboard + */ + public void hideKeyboard(String keyName); + + /** + * Hides the keyboard if it is showing. Available strategies are PRESS_KEY + * and TAP_OUTSIDE. One taps outside the keyboard, the other presses a key + * of your choosing (probably the 'Done' key). Hiding the keyboard often + * depends on the way an app is implemented, no single strategy always + * works. + * + * @param strategy + * HideKeyboardStrategy + * @param keyName + * a String, representing the text displayed on the button of the + * keyboard you want to press. For example: "Done" + */ + public void hideKeyboard(String strategy, String keyName); + + /** + * Simulate shaking the device + */ + public void shake(); + +} diff --git a/src/main/java/io/appium/java_client/ios/IOSDriver.java b/src/main/java/io/appium/java_client/ios/IOSDriver.java new file mode 100644 index 000000000..2950b2774 --- /dev/null +++ b/src/main/java/io/appium/java_client/ios/IOSDriver.java @@ -0,0 +1,95 @@ +package io.appium.java_client.ios; + +import com.google.common.collect.ImmutableMap; + +import io.appium.java_client.AppiumDriver; +import io.appium.java_client.FindsByIosUIAutomation; +import io.appium.java_client.MobileElement; +import io.appium.java_client.ios.internal.JsonToIOSElementConverter; +import io.appium.java_client.remote.MobilePlatform; + +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.WebElement; + +import java.net.URL; +import java.util.List; + +import static io.appium.java_client.MobileCommand.*; + +public class IOSDriver extends AppiumDriver implements IOSDeviceActionShortcuts, GetsNamedTextField, FindsByIosUIAutomation{ + private static final String IOS_PLATFORM = MobilePlatform.IOS; + + public IOSDriver(URL remoteAddress, Capabilities desiredCapabilities) { + super(remoteAddress, substituteMobilePlatform(desiredCapabilities, + IOS_PLATFORM)); + this.setElementConverter(new JsonToIOSElementConverter(this)); + } + + /** + * Scroll to the element whose 'text' attribute contains the input text. + * This scrolling happens within the first UIATableView on the UI. Use the method on IOSElement to scroll from a different ScrollView. + * @param text input text contained in text attribute + */ + @Override + public MobileElement scrollTo(String text) { + return ((IOSElement) findElementByClassName("UIATableView")).scrollTo(text); + } + + /** + * Scroll to the element whose 'text' attribute is equal to the input text. + * This scrolling happens within the first UIATableView on the UI. Use the method on IOSElement to scroll from a different ScrollView. + * @param text input text to match + */ + @Override + public MobileElement scrollToExact(String text) { + return ((IOSElement) findElementByClassName("UIATableView")).scrollToExact(text); + } + + /** + * @see IOSDeviceActionShortcuts#hideKeyboard(String, String) + */ + @Override + public void hideKeyboard(String strategy, String keyName) { + String[] parameters = new String[] { "strategy", "key" }; + Object[] values = new Object[] { strategy, keyName }; + execute(HIDE_KEYBOARD, getCommandImmutableMap(parameters, values)); + } + + /** + * @see IOSDeviceActionShortcuts#hideKeyboard(String) + */ + @Override + public void hideKeyboard(String keyName) { + execute(HIDE_KEYBOARD, ImmutableMap.of("keyName", keyName)); + } + + /** + * @see IOSDeviceActionShortcuts#shake() + */ + @Override + public void shake() { + execute(SHAKE); + } + + /** + * @see GetsNamedTextField#getNamedTextField(String) + */ + @Override + public WebElement getNamedTextField(String name) { + MobileElement element = (MobileElement) findElementByAccessibilityId(name); + if (element.getTagName() != "TextField") { + return element.findElementByAccessibilityId(name); + } + return element; + } + + @Override + public WebElement findElementByIosUIAutomation(String using) { + return findElement("-ios uiautomation", using); + } + + @Override + public List findElementsByIosUIAutomation(String using) { + return findElements("-ios uiautomation", using); + } +} diff --git a/src/main/java/io/appium/java_client/ios/IOSElement.java b/src/main/java/io/appium/java_client/ios/IOSElement.java new file mode 100644 index 000000000..3a4009918 --- /dev/null +++ b/src/main/java/io/appium/java_client/ios/IOSElement.java @@ -0,0 +1,41 @@ +package io.appium.java_client.ios; + +import io.appium.java_client.FindsByIosUIAutomation; +import io.appium.java_client.MobileElement; +import io.appium.java_client.ScrollsTo; +import org.openqa.selenium.WebElement; + +import java.util.List; + +public class IOSElement extends MobileElement implements FindsByIosUIAutomation, ScrollsTo { + + @Override + public WebElement findElementByIosUIAutomation(String using) { + return findElement("-ios uiautomation", using); + } + + @Override + public List findElementsByIosUIAutomation(String using) { + return findElements("-ios uiautomation", using); + } + + /** + * Scroll to the element whose 'text' attribute contains the input text. + * Scrolling happens within this element + * @param text input text contained in text attribute + */ + @Override + public MobileElement scrollTo(String text) { + return (MobileElement) findElementByIosUIAutomation(".scrollToElementWithPredicate(\"name CONTAINS '" + text + "'\")"); + } + + /** + * Scroll to the element whose 'text' attribute matches the input text. + * Scrolling happens within this element + * @param text input text contained in text attribute + */ + @Override + public MobileElement scrollToExact(String text) { + return (MobileElement) findElementByIosUIAutomation(".scrollToElementWithName(\"" + text + "\")"); + } +} diff --git a/src/main/java/io/appium/java_client/IOSKeyCode.java b/src/main/java/io/appium/java_client/ios/IOSKeyCode.java similarity index 90% rename from src/main/java/io/appium/java_client/IOSKeyCode.java rename to src/main/java/io/appium/java_client/ios/IOSKeyCode.java index f76921bb4..0c7268752 100644 --- a/src/main/java/io/appium/java_client/IOSKeyCode.java +++ b/src/main/java/io/appium/java_client/ios/IOSKeyCode.java @@ -15,10 +15,10 @@ +limitations under the License. + */ -package io.appium.java_client; +package io.appium.java_client.ios; /** - * Some common key codes for Android Key Events + * Some common key codes for iOS Key Events */ public interface IOSKeyCode { diff --git a/src/main/java/io/appium/java_client/ios/internal/JsonToIOSElementConverter.java b/src/main/java/io/appium/java_client/ios/internal/JsonToIOSElementConverter.java new file mode 100644 index 000000000..d44be548d --- /dev/null +++ b/src/main/java/io/appium/java_client/ios/internal/JsonToIOSElementConverter.java @@ -0,0 +1,21 @@ +package io.appium.java_client.ios.internal; + +import io.appium.java_client.AppiumDriver; +import io.appium.java_client.MobileElement; +import io.appium.java_client.internal.JsonToMobileElementConverter; +import io.appium.java_client.ios.IOSElement; + +public class JsonToIOSElementConverter extends JsonToMobileElementConverter { + + public JsonToIOSElementConverter(AppiumDriver driver) { + super(driver); + } + + @Override + protected MobileElement newMobileElement() { + IOSElement toReturn = new IOSElement(); + toReturn.setParent(driver); + return toReturn; + } + +} 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 27a05cc80..d35b6628b 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java @@ -52,8 +52,7 @@ public List apply(By by) { private WebElement cachedElement; private List cachedElementList; - private final long implicitlyWaitTimeOut; - private final TimeUnit timeUnit ; + private final TimeOutContainer timeOutContainer; /** * Creates a new mobile element locator. It instantiates {@link WebElement} @@ -64,8 +63,8 @@ public List apply(By by) { * @param field * The field on the Page Object that will hold the located value */ - public AppiumElementLocator(SearchContext searchContext, Field field, - long implicitlyWaitTimeOut, TimeUnit timeUnit) { + AppiumElementLocator(SearchContext searchContext, Field field, + TimeOutContainer timeOutContainer) { this.searchContext = searchContext; // All known webdrivers implement HasCapabilities String platform = String @@ -73,8 +72,7 @@ public AppiumElementLocator(SearchContext searchContext, Field field, .getCapabilities().getCapability( MobileCapabilityType.PLATFORM_NAME)); AppiumAnnotations annotations = new AppiumAnnotations(field, platform); - this.implicitlyWaitTimeOut = implicitlyWaitTimeOut; - this.timeUnit = timeUnit; + this.timeOutContainer = timeOutContainer; shouldCache = annotations.isLookupCached(); by = annotations.buildBy(); } @@ -109,14 +107,15 @@ private List waitFor(){ try{ changeImplicitlyWaitTimeOut(0, TimeUnit.SECONDS); FluentWait wait = new FluentWait(by); - wait.withTimeout(implicitlyWaitTimeOut, timeUnit); + wait.withTimeout(timeOutContainer.getTimeValue(), timeOutContainer.getTimeUnitValue()); return wait.until(new WaitingFunction(searchContext)); } catch (TimeoutException e){ return new ArrayList(); } finally{ - changeImplicitlyWaitTimeOut(implicitlyWaitTimeOut, timeUnit); + changeImplicitlyWaitTimeOut(timeOutContainer.getTimeValue(), + timeOutContainer.getTimeUnitValue()); } } diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java index 8bcbeb62f..b46e484c6 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumElementLocatorFactory.java @@ -8,31 +8,26 @@ 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; + private final SearchContext searchContext; + private final TimeOutContainer timeOutContainer; public AppiumElementLocatorFactory(SearchContext searchContext, long implicitlyWaitTimeOut, TimeUnit timeUnit) { this.searchContext = searchContext; - this.implicitlyWaitTimeOut = implicitlyWaitTimeOut; - this.timeUnit = timeUnit; + this.timeOutContainer = new TimeOutContainer(implicitlyWaitTimeOut, timeUnit); } public AppiumElementLocatorFactory(SearchContext searchContext) { - this(searchContext, DEFAULT_IMPLICITLY_WAIT_TIMEOUT, DEFAULT_TIMEUNIT); + this(searchContext, AppiumFieldDecorator.DEFAULT_IMPLICITLY_WAIT_TIMEOUT, + AppiumFieldDecorator.DEFAULT_TIMEUNIT); } public ElementLocator createLocator(Field field) { - return new AppiumElementLocator(searchContext, field, implicitlyWaitTimeOut, timeUnit); + return new AppiumElementLocator(searchContext, field, timeOutContainer); } @Override public void resetImplicitlyWaitTimeOut(long timeOut, TimeUnit timeUnit) { - implicitlyWaitTimeOut = timeOut; - this.timeUnit = timeUnit; + timeOutContainer.resetImplicitlyWaitTimeOut(timeOut, 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 index ba83dee86..c86e385c5 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -1,116 +1,141 @@ -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) { - Class type = field.getType(); - if (type.equals(WebElement.class)){ - type = RemoteWebElement.class; - } - ElementInterceptor elementInterceptor = new ElementInterceptor(locator); - return ProxyFactory.getEnhancedProxy(type, - 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); - } -} +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; +import org.reflections.Reflections; + +/** + * 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 static final List> availableElementClasses = + new ArrayList>(){ + private static final long serialVersionUID = 1L; + { + add(WebElement.class); + add(RemoteWebElement.class); + add(MobileElement.class); + + Reflections r = new Reflections("io.appium"); + addAll(r.getSubTypesOf(MobileElement.class)); + } + + }; + + private final AppiumElementLocatorFactory factory; + + public static long DEFAULT_IMPLICITLY_WAIT_TIMEOUT = 1; + + public static TimeUnit DEFAULT_TIMEUNIT = TimeUnit.SECONDS; + + 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 (!(availableElementClasses.contains(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 static boolean isAvailableElementClass(Type type){ + boolean result = false; + for (Class webElementClass: + availableElementClasses){ + if (!webElementClass.equals(type)){ + continue; + } + result = true; + break; + } + return result; + } + + 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 (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 isAvailableElementClass(listType); + } + + private Object proxyForLocator(Field field, ElementLocator locator) { + Class type = field.getType(); + if (type.equals(WebElement.class)){ + type = RemoteWebElement.class; + } + ElementInterceptor elementInterceptor = new ElementInterceptor(locator); + return ProxyFactory.getEnhancedProxy(type, + 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/TimeOutContainer.java b/src/main/java/io/appium/java_client/pagefactory/TimeOutContainer.java new file mode 100644 index 000000000..3697c792f --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/TimeOutContainer.java @@ -0,0 +1,32 @@ +package io.appium.java_client.pagefactory; + +import java.util.concurrent.TimeUnit; + +/** + * Instances of this class contain + * implicit time outs which are used by {@link AppiumElementLocator} + */ +class TimeOutContainer implements ResetsImplicitlyWaitTimeOut{ + private long timeOutValue; + private TimeUnit timeUnit; + + TimeOutContainer(long initialTimeOutValue, TimeUnit initialTimeUnit){ + this.timeOutValue = initialTimeOutValue; + this.timeUnit = initialTimeUnit; + } + + @Override + public void resetImplicitlyWaitTimeOut(long timeOut, TimeUnit timeUnit) { + this.timeOutValue = timeOut; + this.timeUnit = timeUnit; + } + + long getTimeValue(){ + return timeOutValue; + } + + TimeUnit getTimeUnitValue(){ + return timeUnit; + } + +} diff --git a/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java b/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java index d822b2a02..727a0a5ad 100644 --- a/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java +++ b/src/main/java/io/appium/java_client/remote/MobileCapabilityType.java @@ -20,4 +20,5 @@ public interface MobileCapabilityType extends CapabilityType { String APP_ACTIVITY = "appActivity"; String APP_WAIT_ACTIVITY = "appWaitActivity"; String APP_WAIT_PACKAGE = "appWaitPackage"; + String SELENDROID_PORT = "selendroidPort"; } diff --git a/src/test/java/io/appium/java_client/MobileDriverIOSTest.java b/src/test/java/io/appium/java_client/AppiumDriverTest.java similarity index 64% rename from src/test/java/io/appium/java_client/MobileDriverIOSTest.java rename to src/test/java/io/appium/java_client/AppiumDriverTest.java index 0256576a4..5ad922ab6 100644 --- a/src/test/java/io/appium/java_client/MobileDriverIOSTest.java +++ b/src/test/java/io/appium/java_client/AppiumDriverTest.java @@ -17,10 +17,9 @@ package io.appium.java_client; -import io.appium.java_client.remote.HideKeyboardStrategy; +import io.appium.java_client.ios.IOSDriver; import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.remote.MobilePlatform; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -36,7 +35,7 @@ /** * Test Mobile Driver features */ -public class MobileDriverIOSTest { +public class AppiumDriverTest { private AppiumDriver driver; @@ -50,7 +49,7 @@ public void setup() throws Exception { capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.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); + driver = new IOSDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); } @After @@ -65,19 +64,10 @@ public void resetTest() { @Test public void setValueTest() { - MobileElement element = (MobileElement)driver.findElementByAccessibilityId("TextFields, Uses of UITextField"); - element.click(); - element = (MobileElement)driver.findElementByAccessibilityId("Normal"); - element.setValue("Grace Hopper"); - } - - @Test - public void namedTextFieldTest() { - MobileElement element = (MobileElement)driver.findElementByAccessibilityId("TextFields, Uses of UITextField"); + MobileElement element = (MobileElement)driver.findElementByAccessibilityId("Text Fields, AAPLTextFieldViewController"); element.click(); - element = (MobileElement)driver.getNamedTextField("Normal"); + element = (MobileElement)driver.findElementByAccessibilityId("DEFAULT"); element.setValue("Grace Hopper"); - assertEquals("Grace Hopper", element.getText()); } @Test @@ -86,32 +76,7 @@ public void pullFileTest() { assert(data.length > 0); } - @Test - public void hideKeyboardTest() { - MobileElement element = (MobileElement)driver.findElementByAccessibilityId("TextFields, Uses of UITextField"); - element.click(); - element = (MobileElement)driver.findElementByAccessibilityId("Normal"); - element.click(); - driver.hideKeyboard(); - } - - @Test - public void hideKeyboardWithParametersTest() { - MobileElement element = (MobileElement)driver.findElementByAccessibilityId("TextFields, Uses of UITextField"); - element.click(); - element = (MobileElement)driver.findElementByAccessibilityId("Normal"); - element.click(); - driver.hideKeyboard(HideKeyboardStrategy.PRESS_KEY, "Done"); - } - - @Test - public void hideKeyboardWithNullParameterTest() { - MobileElement element = (MobileElement)driver.findElementByAccessibilityId("TextFields, Uses of UITextField"); - element.click(); - element = (MobileElement)driver.findElementByAccessibilityId("Normal"); - element.click(); - driver.hideKeyboard(HideKeyboardStrategy.TAP_OUTSIDE, null); - } + //TODO hideKeyboard() test @Test public void runAppInBackgroundTest() { diff --git a/src/test/java/io/appium/java_client/ContextTest.java b/src/test/java/io/appium/java_client/ContextTest.java index 8639d3a12..cef95a3d8 100644 --- a/src/test/java/io/appium/java_client/ContextTest.java +++ b/src/test/java/io/appium/java_client/ContextTest.java @@ -18,8 +18,10 @@ package io.appium.java_client; import static org.junit.Assert.assertEquals; +import io.appium.java_client.AppiumDriver; +import io.appium.java_client.NoSuchContextException; +import io.appium.java_client.ios.IOSDriver; import io.appium.java_client.remote.MobileCapabilityType; -import io.appium.java_client.remote.MobilePlatform; import java.io.File; import java.net.URL; @@ -43,10 +45,9 @@ public void setup() throws Exception { DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, ""); capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "7.1"); - capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.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); + driver = new IOSDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); } @After diff --git a/src/test/java/io/appium/java_client/UICatalog.app.zip b/src/test/java/io/appium/java_client/UICatalog.app.zip old mode 100755 new mode 100644 index ad64ffa46..911811635 Binary files a/src/test/java/io/appium/java_client/UICatalog.app.zip and b/src/test/java/io/appium/java_client/UICatalog.app.zip differ diff --git a/src/test/java/io/appium/java_client/android/AndroidAccessibilityTest.java b/src/test/java/io/appium/java_client/android/AndroidAccessibilityTest.java new file mode 100644 index 000000000..a23fb18d1 --- /dev/null +++ b/src/test/java/io/appium/java_client/android/AndroidAccessibilityTest.java @@ -0,0 +1,63 @@ +package io.appium.java_client.android; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import io.appium.java_client.AppiumDriver; +import io.appium.java_client.MobileBy; +import io.appium.java_client.android.AndroidDriver; +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.Before; +import org.junit.Test; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.DesiredCapabilities; + +public class AndroidAccessibilityTest { + + private AppiumDriver driver; + + @Before + public void setUp() throws Exception { + File appDir = new File("src/test/java/io/appium/java_client"); + File app = new File(appDir, "ApiDemos-debug.apk"); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); + capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); + } + + @After + public void tearDown() throws Exception { + driver.quit(); + } + + @Test + public void findElementsTest() { + List elements = driver.findElementsByAccessibilityId("Accessibility"); + assertTrue(elements.size() > 0); + } + + @Test + public void findElementTest() { + WebElement element = driver.findElementByAccessibilityId("Accessibility"); + assertNotNull(element); + } + + @Test + public void MobileElementByTest() { + WebElement element = driver.findElement(MobileBy.AccessibilityId("Accessibility")); + assertNotNull(element); + } + + @Test + public void MobileElementsByTest() { + List elements = driver.findElements(MobileBy.AccessibilityId("Accessibility")); + assertTrue(elements.size() > 0); + } + +} diff --git a/src/test/java/io/appium/java_client/MobileDriverAndroidTest.java b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java similarity index 84% rename from src/test/java/io/appium/java_client/MobileDriverAndroidTest.java rename to src/test/java/io/appium/java_client/android/AndroidDriverTest.java index 2a4cbfd71..8073df021 100644 --- a/src/test/java/io/appium/java_client/MobileDriverAndroidTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java @@ -15,15 +15,16 @@ +limitations under the License. + */ -package io.appium.java_client; +package io.appium.java_client.android; +import io.appium.java_client.AppiumSetting; +import io.appium.java_client.NetworkConnectionSetting; import io.appium.java_client.remote.MobileCapabilityType; -import io.appium.java_client.remote.MobilePlatform; - import org.apache.commons.codec.binary.Base64; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.openqa.selenium.WebElement; import org.openqa.selenium.remote.DesiredCapabilities; import java.io.File; @@ -34,9 +35,9 @@ /** * Test Mobile Driver features */ -public class MobileDriverAndroidTest { +public class AndroidDriverTest { - private AppiumDriver driver; + private AndroidDriver driver; @Before public void setup() throws Exception { @@ -45,10 +46,9 @@ public void setup() throws Exception { DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, ""); capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); - capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.ANDROID); capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); capabilities.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, 120); - driver = new AppiumDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); + driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); } @After @@ -141,16 +141,32 @@ public void ignoreUnimportantViews() { } @Test - public void startActivityInThisAppTest(){ + public void startActivityInThisAppTest() { driver.startActivity("io.appium.android.apis", ".accessibility.AccessibilityNodeProviderActivity", null, null); String activity = driver.currentActivity(); assertTrue(activity.contains("Node")); } - @Test - public void startActivityInAnotherAppTest(){ + @Test + public void startActivityInAnotherAppTest() { driver.startActivity("com.android.contacts", ".ContactsListActivity", null, null); String activity = driver.currentActivity(); assertTrue(activity.contains("Contact")); } + + //TODO hideKeyboard() test + + @Test + public void scrollToTest() { + driver.scrollTo("View"); + WebElement views = driver.findElementByAccessibilityId("Views"); + assertNotNull(views); + } + + @Test + public void scrollToExactTest() { + driver.scrollToExact("Views"); + WebElement views = driver.findElementByAccessibilityId("Views"); + assertNotNull(views); + } } diff --git a/src/test/java/io/appium/java_client/AndroidGestureTest.java b/src/test/java/io/appium/java_client/android/AndroidGestureTest.java similarity index 90% rename from src/test/java/io/appium/java_client/AndroidGestureTest.java rename to src/test/java/io/appium/java_client/android/AndroidGestureTest.java index a7c3ac6ce..a35a58a3b 100644 --- a/src/test/java/io/appium/java_client/AndroidGestureTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidGestureTest.java @@ -15,10 +15,18 @@ +limitations under the License. + */ -package io.appium.java_client; +package io.appium.java_client.android; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import io.appium.java_client.MobileBy; +import io.appium.java_client.MultiTouchAction; +import io.appium.java_client.TouchAction; +import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.remote.MobileCapabilityType; -import io.appium.java_client.remote.MobilePlatform; + +import java.io.File; +import java.net.URL; import org.junit.After; import org.junit.Before; @@ -27,17 +35,11 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.remote.DesiredCapabilities; -import java.io.File; -import java.net.URL; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; - /** * Test Mobile Driver features */ public class AndroidGestureTest { - private AppiumDriver driver; + private AndroidDriver driver; @Before public void setup() throws Exception { @@ -46,9 +48,8 @@ public void setup() throws Exception { DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, ""); capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); - capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.ANDROID); capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); - driver = new AppiumDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); + driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); } @After diff --git a/src/test/java/io/appium/java_client/AndroidUIAutomatorTest.java b/src/test/java/io/appium/java_client/android/AndroidUIAutomatorTest.java similarity index 81% rename from src/test/java/io/appium/java_client/AndroidUIAutomatorTest.java rename to src/test/java/io/appium/java_client/android/AndroidUIAutomatorTest.java index 074a3e447..3c530dd69 100644 --- a/src/test/java/io/appium/java_client/AndroidUIAutomatorTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidUIAutomatorTest.java @@ -1,7 +1,11 @@ -package io.appium.java_client; +package io.appium.java_client.android; +import io.appium.java_client.MobileBy; +import io.appium.java_client.MobileElement; +import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.remote.MobilePlatform; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -20,7 +24,7 @@ */ public class AndroidUIAutomatorTest { - private AppiumDriver driver; + private AndroidDriver driver; @Before public void setup() throws Exception { @@ -31,7 +35,7 @@ public void setup() throws Exception { capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator"); capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.ANDROID); capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); - driver = new AppiumDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); + driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); } @After @@ -65,8 +69,8 @@ public void findElementsByTest() { @Test public void findChainedElementsTest() { - MobileElement el1 = (MobileElement) driver.findElementByAndroidUIAutomator("resourceId(\"android:id/content\")"); - MobileElement el2 = (MobileElement) el1.findElementByAndroidUIAutomator("text(\"Accessibility\")"); + AndroidElement el1 = (AndroidElement) driver.findElementByAndroidUIAutomator("resourceId(\"android:id/content\")"); + AndroidElement el2 = (AndroidElement) el1.findElementByAndroidUIAutomator("text(\"Accessibility\")"); el2.click(); MobileElement el3 = (MobileElement) driver.findElementByAndroidUIAutomator("text(\"Custom View\")"); assertTrue(el3.isDisplayed()); diff --git a/src/test/java/io/appium/java_client/AccessibilityIdTest.java b/src/test/java/io/appium/java_client/ios/IOSAccessibilityIdTest.java similarity index 85% rename from src/test/java/io/appium/java_client/AccessibilityIdTest.java rename to src/test/java/io/appium/java_client/ios/IOSAccessibilityIdTest.java index 4b404dfb7..472ca7a5a 100644 --- a/src/test/java/io/appium/java_client/AccessibilityIdTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSAccessibilityIdTest.java @@ -1,9 +1,11 @@ -package io.appium.java_client; +package io.appium.java_client.ios; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import io.appium.java_client.AppiumDriver; +import io.appium.java_client.MobileBy; +import io.appium.java_client.ios.IOSDriver; import io.appium.java_client.remote.MobileCapabilityType; -import io.appium.java_client.remote.MobilePlatform; import java.io.File; import java.net.URL; @@ -18,7 +20,7 @@ /** * Test context-related features */ -public class AccessibilityIdTest { +public class IOSAccessibilityIdTest { private AppiumDriver driver; @@ -29,10 +31,9 @@ public void setup() throws Exception { DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, ""); capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "7.1"); - capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.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); + driver = new IOSDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); } @After diff --git a/src/test/java/io/appium/java_client/ios/IOSDriverTest.java b/src/test/java/io/appium/java_client/ios/IOSDriverTest.java new file mode 100644 index 000000000..ab9c94b2e --- /dev/null +++ b/src/test/java/io/appium/java_client/ios/IOSDriverTest.java @@ -0,0 +1,104 @@ +/* + +Copyright 2014 Appium contributors + +Copyright 2014 Software Freedom Conservancy + + + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + + + + http://www.apache.org/licenses/LICENSE-2.0 + + + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + + */ + +package io.appium.java_client.ios; + +import io.appium.java_client.MobileElement; +import io.appium.java_client.remote.HideKeyboardStrategy; +import io.appium.java_client.remote.MobileCapabilityType; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.openqa.selenium.Point; + +import org.openqa.selenium.remote.DesiredCapabilities; + +import java.io.File; +import java.net.URL; + +import static org.junit.Assert.assertEquals; + +import static org.junit.Assert.assertNotEquals; + + +/** + * Test Mobile Driver features + */ +public class IOSDriverTest { + + private IOSDriver driver; + + @Before + public void setup() throws Exception { + File appDir = new File("src/test/java/io/appium/java_client"); + File app = new File(appDir, "UICatalog.app.zip"); + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, ""); + capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "7.1"); + capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "iPhone Simulator"); + capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); + driver = new IOSDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); + } + + @After + public void tearDown() throws Exception { + driver.quit(); + } + + @Test + public void resetTest() { + driver.resetApp(); + } + + @Test + public void namedTextFieldTest() { + MobileElement element = (MobileElement)driver.findElementByAccessibilityId("Text Fields, AAPLTextFieldViewController"); + element.click(); + element = (MobileElement)driver.getNamedTextField("DEFAULT"); + element.setValue("Grace Hopper"); + assertEquals("Grace Hopper", element.getText()); + } + + @Test + public void hideKeyboardWithParametersTest() { + MobileElement element = (MobileElement)driver.findElementByAccessibilityId("Text Fields, AAPLTextFieldViewController"); + element.click(); + element = (MobileElement)driver.findElementByAccessibilityId("DEFAULT"); + element.click(); + driver.hideKeyboard(HideKeyboardStrategy.PRESS_KEY, "Done"); + } + + @Test + public void scrollToTest() { + MobileElement searchBar = (MobileElement) driver.findElementByName("Search Bars"); + Point before = searchBar.getLocation(); + driver.scrollTo("Search Ba"); + Point after = searchBar.getLocation(); + assertNotEquals(before, after); + } + + @Test + public void scrollToExactTest() { + MobileElement searchBar = (MobileElement) driver.findElementByName("Search Bars"); + Point before = searchBar.getLocation(); + driver.scrollToExact("Search Bars"); + Point after = searchBar.getLocation(); + assertNotEquals(before, after); + } +} diff --git a/src/test/java/io/appium/java_client/IosUIAutomationTest.java b/src/test/java/io/appium/java_client/ios/IosUIAutomationTest.java similarity index 87% rename from src/test/java/io/appium/java_client/IosUIAutomationTest.java rename to src/test/java/io/appium/java_client/ios/IosUIAutomationTest.java index 0250259ef..62d72f9f3 100644 --- a/src/test/java/io/appium/java_client/IosUIAutomationTest.java +++ b/src/test/java/io/appium/java_client/ios/IosUIAutomationTest.java @@ -1,7 +1,8 @@ -package io.appium.java_client; +package io.appium.java_client.ios; +import io.appium.java_client.MobileBy; +import io.appium.java_client.ios.IOSDriver; import io.appium.java_client.remote.MobileCapabilityType; -import io.appium.java_client.remote.MobilePlatform; import org.junit.After; import org.junit.Before; @@ -20,7 +21,7 @@ */ public class IosUIAutomationTest { - private AppiumDriver driver; + private IOSDriver driver; @Before public void setup() throws Exception { @@ -29,10 +30,9 @@ public void setup() throws Exception { DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, ""); capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "7.1"); - capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.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); + driver = new IOSDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); } @After diff --git a/src/test/java/io/appium/java_client/iOSGestureTest.java b/src/test/java/io/appium/java_client/ios/iOSGestureTest.java similarity index 93% rename from src/test/java/io/appium/java_client/iOSGestureTest.java rename to src/test/java/io/appium/java_client/ios/iOSGestureTest.java index f3307c589..a0b15e66b 100644 --- a/src/test/java/io/appium/java_client/iOSGestureTest.java +++ b/src/test/java/io/appium/java_client/ios/iOSGestureTest.java @@ -15,10 +15,13 @@ +limitations under the License. + */ -package io.appium.java_client; +package io.appium.java_client.ios; +import io.appium.java_client.AppiumDriver; +import io.appium.java_client.MultiTouchAction; +import io.appium.java_client.TouchAction; +import io.appium.java_client.ios.IOSDriver; import io.appium.java_client.remote.MobileCapabilityType; -import io.appium.java_client.remote.MobilePlatform; import java.io.File; import java.net.URL; @@ -46,10 +49,9 @@ public void setup() throws Exception { DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, ""); capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "7.1"); - capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.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); + driver = new IOSDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); } @After 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 0704c39f7..66848b2e2 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 @@ -1,24 +1,26 @@ package io.appium.java_client.pagefactory_tests; -import io.appium.java_client.AppiumDriver; import io.appium.java_client.MobileElement; +import io.appium.java_client.android.AndroidDriver; +import io.appium.java_client.android.AndroidElement; 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 io.appium.java_client.remote.MobilePlatform; import java.io.File; import java.net.URL; import java.util.List; +import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.openqa.selenium.NoSuchElementException; +import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.RemoteWebElement; @@ -27,7 +29,7 @@ public class AndroidPageObjectTest { - private AppiumDriver driver; + private WebDriver driver; @FindBy(className = "android.widget.TextView") private List textVieWs; @@ -64,6 +66,7 @@ public class AndroidPageObjectTest { private List iosChainTextViews; @AndroidFindBys({ + @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")"), @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")"), @AndroidFindBy(id = "android:id/text1") }) @@ -71,7 +74,6 @@ public class AndroidPageObjectTest { @iOSFindBy(xpath = "//someElement")}) private List chainAndroidOrIOSUIAutomatorViews; - @FindBy(id = "android:id/text1") private WebElement textView; @@ -108,25 +110,39 @@ public class AndroidPageObjectTest { private WebElement iosChainTextView; @AndroidFindBys({ + @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")"), @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")"), @AndroidFindBy(id = "android:id/text1") }) @iOSFindBys({@iOSFindBy(uiAutomator = ".elements()[0]"), @iOSFindBy(xpath = "//someElement")}) private WebElement chainAndroidOrIOSUIAutomatorView; + + @AndroidFindBys({ + @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")"), + @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")"), + @AndroidFindBy(id = "android:id/text1") + }) + private AndroidElement androidElementView; + + @AndroidFindBys({ + @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")"), + @AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")"), + @AndroidFindBy(id = "android:id/text1") + }) + private List androidElementViews; @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, MobilePlatform.ANDROID); capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); - driver = new AppiumDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); + driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); - PageFactory.initElements(new AppiumFieldDecorator(driver), this); + //This time out is set because test can be run on slow Android SDK emulator + PageFactory.initElements(new AppiumFieldDecorator(driver, 5, TimeUnit.SECONDS), this); } @After @@ -257,5 +273,15 @@ public void androidOrIOSFindByElementsTest_ChainSearches(){ @Test public void androidOrIOSFindByElementTest_ChainSearches(){ Assert.assertNotEquals(null, chainAndroidOrIOSUIAutomatorView.getAttribute("text")); - } + } + + @Test + public void isAndroidElementTest(){ + Assert.assertNotEquals(null, androidElementView.getAttribute("text")); + } + + @Test + public void areAndroidElementsTest(){ + Assert.assertNotEquals(0, androidElementViews.size()); + } } diff --git a/src/test/java/io/appium/java_client/pagefactory_tests/TimeOutResetTest.java b/src/test/java/io/appium/java_client/pagefactory_tests/TimeOutResetTest.java new file mode 100644 index 000000000..0b35df625 --- /dev/null +++ b/src/test/java/io/appium/java_client/pagefactory_tests/TimeOutResetTest.java @@ -0,0 +1,85 @@ +package io.appium.java_client.pagefactory_tests; + +import io.appium.java_client.pagefactory.AppiumFieldDecorator; + +import java.util.Calendar; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.firefox.FirefoxDriver; +import org.openqa.selenium.support.FindAll; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.PageFactory; + +public class TimeOutResetTest { + private WebDriver driver; + private final static long ACCEPTABLE_DELTA_MILLS = 500; + + + @FindAll({@FindBy(className = "ClassWhichDoesNotExist"), + @FindBy(className = "OneAnotherClassWhichDoesNotExist")}) + private List stubElements; + private AppiumFieldDecorator afd; + + @Before + public void setUp() throws Exception { + driver = new FirefoxDriver(); + afd = new AppiumFieldDecorator(driver); + + PageFactory.initElements(afd, this); + } + + @After + public void tearDown() throws Exception { + driver.quit(); + } + + private static void checkTimeDifference(long etalonTime, + TimeUnit etalonTimeUnit, long currentMillis) { + long etalonMillis = TimeUnit.MILLISECONDS.convert(etalonTime, + etalonTimeUnit); + try{ + Assert.assertEquals(true, + ((currentMillis - etalonMillis) < ACCEPTABLE_DELTA_MILLS) + && ((currentMillis - etalonMillis) >= 0)); + } + catch (Error e){ + String message = String.valueOf(etalonTime) + " " + etalonTimeUnit.toString() + " current duration in millis " + + String.valueOf(currentMillis) + " Failed"; + throw new RuntimeException(message, e); + } + } + + private long getBenchMark() { + long startMark = Calendar.getInstance().getTimeInMillis(); + stubElements.size(); + long endMark = Calendar.getInstance().getTimeInMillis(); + return endMark - startMark; + } + + @Test + public void test() { + checkTimeDifference(AppiumFieldDecorator.DEFAULT_IMPLICITLY_WAIT_TIMEOUT, AppiumFieldDecorator.DEFAULT_TIMEUNIT, + getBenchMark()); + System.out.println(String.valueOf(AppiumFieldDecorator.DEFAULT_IMPLICITLY_WAIT_TIMEOUT) + + " " + AppiumFieldDecorator.DEFAULT_TIMEUNIT.toString() + ": Fine"); + + afd.resetImplicitlyWaitTimeOut(15500000, TimeUnit.MICROSECONDS); + checkTimeDifference(15500000, TimeUnit.MICROSECONDS, getBenchMark()); + System.out.println("Change time: " + String.valueOf(15500000) + " " + + TimeUnit.MICROSECONDS.toString() + ": Fine"); + + afd.resetImplicitlyWaitTimeOut(3, TimeUnit.SECONDS); + checkTimeDifference(3, TimeUnit.SECONDS, getBenchMark()); + System.out.println("Change time: " + String.valueOf(3) + " " + + TimeUnit.SECONDS.toString() + ": Fine"); + + } + +} 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 index 8d8975163..36ddf468b 100644 --- a/src/test/java/io/appium/java_client/pagefactory_tests/iOSPageObjectTest.java +++ b/src/test/java/io/appium/java_client/pagefactory_tests/iOSPageObjectTest.java @@ -1,13 +1,13 @@ package io.appium.java_client.pagefactory_tests; -import io.appium.java_client.AppiumDriver; import io.appium.java_client.MobileElement; +import io.appium.java_client.ios.IOSDriver; +import io.appium.java_client.ios.IOSElement; 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 io.appium.java_client.remote.MobilePlatform; import java.io.File; import java.net.URL; @@ -18,6 +18,7 @@ import org.junit.Before; import org.junit.Test; import org.openqa.selenium.NoSuchElementException; +import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.RemoteWebElement; @@ -26,7 +27,7 @@ public class iOSPageObjectTest { - private AppiumDriver driver; + private WebDriver driver; @FindBy(className = "UIAButton") private List uiButtons; @@ -89,6 +90,12 @@ public class iOSPageObjectTest { @AndroidFindBy(className = "android.widget.TextView") }) private WebElement chainElementView; + + @iOSFindBy(uiAutomator = ".elements()[0]") + private IOSElement iosButton; + + @iOSFindBy(uiAutomator = ".elements()[0]") + private List iosButtons; @Before public void setUp() throws Exception { @@ -97,10 +104,9 @@ public void setUp() throws Exception { DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, ""); capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "7.1"); - capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, MobilePlatform.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); + driver = new IOSDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); PageFactory.initElements(new AppiumFieldDecorator(driver), this); } @@ -214,4 +220,14 @@ public void checkThatElementWasNotFoundByAndroidUIAutomator_Chain(){ } Assert.assertNotNull(nsee); } + + @Test + public void isIOSElementTest(){ + Assert.assertNotEquals(null, iosButton.getText()); + } + + @Test + public void areIOSElements_FindByTest(){ + Assert.assertNotEquals(0, iosButtons.size()); + } } diff --git a/src/test/java/io/appium/java_client/selendroid-test-app-0.9.0.apk b/src/test/java/io/appium/java_client/selendroid-test-app-0.9.0.apk deleted file mode 100644 index a7b39a96c..000000000 Binary files a/src/test/java/io/appium/java_client/selendroid-test-app-0.9.0.apk and /dev/null differ