diff --git a/java-client.iml b/java-client.iml index 961a621f6..e881e4a50 100644 --- a/java-client.iml +++ b/java-client.iml @@ -19,6 +19,7 @@ + diff --git a/src/io/appium/java_client/AppiumDriver.java b/src/io/appium/java_client/AppiumDriver.java index c07634e1d..aa37e209f 100644 --- a/src/io/appium/java_client/AppiumDriver.java +++ b/src/io/appium/java_client/AppiumDriver.java @@ -21,6 +21,7 @@ import org.openqa.selenium.*; import org.openqa.selenium.remote.*; +import javax.xml.bind.DatatypeConverter; import java.net.URL; import java.util.LinkedHashSet; import java.util.List; @@ -45,6 +46,10 @@ public AppiumDriver(URL remoteAddress, Capabilities desiredCapabilities){ .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(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")) ; ImmutableMap mobileCommands = builder.build(); @@ -71,19 +76,39 @@ protected Response execute(String command) { } + /** + * Reset the currently running app for this session + */ public void resetApp() { execute(MobileCommand.RESET); } + /** + * Get all defined Strings from an Android app + * + * @return a string of all the localized strings defined in the app + */ public String getAppStrings() { Response response = execute(GET_STRINGS); 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); @@ -92,11 +117,65 @@ public void sendKeyEvent(int key, Integer metastate) { 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(); } + /** + * + * @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()); + } + + /** + * Hides the keyboard if it is showing. + * This is an iOS only command. + */ + public void hideKeyboard() { + execute(HIDE_KEYBOARD); + } + + /** + * 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)); + } + @Override public WebDriver context(String name) { diff --git a/src/io/appium/java_client/MobileCommand.java b/src/io/appium/java_client/MobileCommand.java index d7b2c383d..062186d22 100644 --- a/src/io/appium/java_client/MobileCommand.java +++ b/src/io/appium/java_client/MobileCommand.java @@ -29,6 +29,10 @@ public interface MobileCommand { String KEY_EVENT = "keyEvent"; String CURRENT_ACTIVITY = "currentActivity"; String SET_VALUE = "setValue"; + String PULL_FILE = "pullFile"; + String PUSH_FILE = "pushFile"; + String HIDE_KEYBOARD = "hideKeyboard"; + String RUN_APP_IN_BACKGROUND = "runAppInBackground"; } diff --git a/src/io/appium/java_client/MobileDriver.java b/src/io/appium/java_client/MobileDriver.java index a1151e378..80453b202 100644 --- a/src/io/appium/java_client/MobileDriver.java +++ b/src/io/appium/java_client/MobileDriver.java @@ -18,45 +18,15 @@ 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 { +public interface MobileDriver extends WebDriver, ContextAware { public Response execute(String driverCommand, Map parameters); - /** - * Reset the currently running app for this session - */ - void resetApp(); - - /** - * Get all defined Strings from an Android app - */ - String getAppStrings(); - - /** - * Send a key event to the device - * - * @param key code for the key pressed on the device - */ - void sendKeyEvent(int key); - - /** - * 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 - */ - void sendKeyEvent(int key, Integer metastate); - - /** - * Get the current activity being run on the mobile device - */ - String currentActivity(); - } diff --git a/test/io/appium/java_client/AndroidUIAutomatorTest.java b/test/io/appium/java_client/AndroidUIAutomatorTest.java index f0f5df2fa..73ff2c7e3 100644 --- a/test/io/appium/java_client/AndroidUIAutomatorTest.java +++ b/test/io/appium/java_client/AndroidUIAutomatorTest.java @@ -1,5 +1,6 @@ package io.appium.java_client; +import org.apache.commons.codec.binary.Base64; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -65,4 +66,13 @@ public void findElementsByTest() { public void ErrorTest() { driver.findElementByAndroidUIAutomator(null); } + + @Test + public void pushFileTest() { + byte[] data = Base64.encodeBase64("The eventual code is no more than the deposit of your understanding. ~E. W. Dijkstra".getBytes()); + driver.pushFile("/data/local/tmp/remote.txt", data); + byte[] returnData = driver.pullFile("/data/local/tmp/remote.txt"); + String returnDataDecoded = new String(Base64.decodeBase64(returnData)); + assertEquals("The eventual code is no more than the deposit of your understanding. ~E. W. Dijkstra", returnDataDecoded); + } } \ No newline at end of file diff --git a/test/io/appium/java_client/MobileDriverIOSTest.java b/test/io/appium/java_client/MobileDriverIOSTest.java index e11b2fe46..ee0fe0989 100644 --- a/test/io/appium/java_client/MobileDriverIOSTest.java +++ b/test/io/appium/java_client/MobileDriverIOSTest.java @@ -65,4 +65,27 @@ public void setValueTest() { element.setValue("Grace Hopper"); } + @Test + public void pullFileTest() { + byte[] data = driver.pullFile("Library/AddressBook/AddressBook.sqlitedb"); + assert(data.length > 0); + } + + @Test + public void hideKeyboardTest() { + MobileElement element = new MobileElement((RemoteWebElement)driver.findElementByAccessibilityId("TextFields, Uses of UITextField"), driver); + element.click(); + element = new MobileElement((RemoteWebElement)driver.findElementByAccessibilityId("Normal"), driver); + element.click(); + driver.hideKeyboard(); + } + + @Test + public void runAppInBackgroundTest() { + long time = System.currentTimeMillis(); + driver.runAppInBackground(4); + long timeAfter = System.currentTimeMillis(); + assert(timeAfter - time > 3000); + } + }