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 extends WebElement> 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