diff --git a/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandExecutor.java b/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandExecutor.java index ca61debf5..8c8db79ed 100644 --- a/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandExecutor.java +++ b/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandExecutor.java @@ -13,6 +13,7 @@ import io.appium.android.bootstrap.handler.GetText; import io.appium.android.bootstrap.handler.Orientation; import io.appium.android.bootstrap.handler.SetText; import io.appium.android.bootstrap.handler.Swipe; +import io.appium.android.bootstrap.handler.TouchLongClick; import io.appium.android.bootstrap.handler.WaitForIdle; import io.appium.android.bootstrap.handler.Wake; @@ -36,6 +37,7 @@ class AndroidCommandExecutor { map.put("swipe", new Swipe()); map.put("flick", new Flick()); map.put("click", new Click()); + map.put("touchLongClick", new TouchLongClick()); map.put("getText", new GetText()); map.put("setText", new SetText()); map.put("getName", new GetName()); @@ -49,7 +51,7 @@ class AndroidCommandExecutor { /** * Gets the handler out of the map, and executes the command. * - * @param command + * @param command The {@link AndroidCommand} * @return {@link AndroidCommandResult} * @throws AndroidCommandException */ diff --git a/android/bootstrap/src/io/appium/android/bootstrap/AndroidElement.java b/android/bootstrap/src/io/appium/android/bootstrap/AndroidElement.java index b04a8dbc1..4082f44fc 100644 --- a/android/bootstrap/src/io/appium/android/bootstrap/AndroidElement.java +++ b/android/bootstrap/src/io/appium/android/bootstrap/AndroidElement.java @@ -35,6 +35,10 @@ public class AndroidElement { return el.click(); } + public boolean longClick() throws UiObjectNotFoundException { + return el.longClick(); + } + public Point getAbsolutePosition(final Double X, final Double Y) throws UiObjectNotFoundException, InvalidCoordinatesException { final Point point = new Point(X, Y); @@ -166,5 +170,4 @@ public class AndroidElement { public boolean setText(final String text) throws UiObjectNotFoundException { return el.setText(text); } - } diff --git a/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchLongClick.java b/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchLongClick.java new file mode 100644 index 000000000..4c790773d --- /dev/null +++ b/android/bootstrap/src/io/appium/android/bootstrap/handler/TouchLongClick.java @@ -0,0 +1,40 @@ +package io.appium.android.bootstrap.handler; + +import com.android.uiautomator.core.UiObjectNotFoundException; +import io.appium.android.bootstrap.*; +import io.appium.android.bootstrap.exceptions.ElementNotInHashException; +import org.json.JSONException; + +/** + * This handler is used to long click elements in the Android UI. + * + */ +public class TouchLongClick extends CommandHandler { + + /** + * + * @param command The {@link AndroidCommand} + * @return {@link AndroidCommandResult} + * @throws JSONException + * @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android.bootstrap.AndroidCommand) + */ + @Override + public AndroidCommandResult execute(AndroidCommand command) throws JSONException { + if (!command.isElementCommand()) { + return getErrorResult("Unable to long click without an element."); + } + + try { + final AndroidElement el = command.getElement(); + final boolean res = el.longClick(); + return getSuccessResult(res); + + } catch (final UiObjectNotFoundException e) { + return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage()); + } catch (final ElementNotInHashException e) { + return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, e.getMessage()); + } catch (final Exception e) { + return getErrorResult(e.getMessage()); + } + } +} diff --git a/app/android.js b/app/android.js index 3f514f36d..30047e6b3 100644 --- a/app/android.js +++ b/app/android.js @@ -363,6 +363,10 @@ Android.prototype.click = function(elementId, cb) { this.proxy(["element:click", {elementId: elementId}], cb); }; +Android.prototype.touchLongClick = function(elementId, cb) { + this.proxy(["element:touchLongClick", {elementId: elementId}], cb); +}; + Android.prototype.fireEvent = function(evt, elementId, value, cb) { cb(new NotYetImplementedError(), null); }; diff --git a/app/controller.js b/app/controller.js index 4ce3c5c01..dff5f58a4 100644 --- a/app/controller.js +++ b/app/controller.js @@ -278,6 +278,13 @@ exports.doClick = function(req, res) { req.device.click(elementId, getResponseHandler(req, res)); }; +exports.touchLongClick = function(req, res) { + var element = req.body.element; + if (checkMissingParams(res, {element: element}, true)) { + req.device.touchLongClick(element, getResponseHandler(req, res)); + } +}; + exports.fireEvent = function(req, res) { var elementId = req.body.element , evt = req.body.event; diff --git a/app/firefoxos.js b/app/firefoxos.js index c5c514daa..7dac09df0 100644 --- a/app/firefoxos.js +++ b/app/firefoxos.js @@ -237,6 +237,7 @@ Firefox.prototype.notImplementedCmds = function() { , 'fireEvent' , 'complexTap' , 'flick' + , 'touchLongClick' , 'getPageSourceXML' , 'swipe' , 'hideKeyboard' diff --git a/app/ios.js b/app/ios.js index e29e9b0ba..f65e59bac 100644 --- a/app/ios.js +++ b/app/ios.js @@ -852,6 +852,10 @@ IOS.prototype.click = function(elementId, cb) { } }; +IOS.prototype.touchLongClick = function(elementId, cb) { + cb(new NotYetImplementedError(), null); +}; + IOS.prototype.fireEvent = function(evt, elementId, cb) { if (this.curWindowHandle) { this.useAtomsElement(elementId, cb, _.bind(function(atomsElement) { diff --git a/app/routing.js b/app/routing.js index 89bfe70f4..b53858d27 100644 --- a/app/routing.js +++ b/app/routing.js @@ -114,6 +114,8 @@ module.exports = function(appium) { rest.get('/wd/hub/session/:sessionId/screenshot', controller.getScreenshot); rest.post('/wd/hub/session/:sessionId?/element/:elementId?/element', controller.findElementFromElement); rest.post('/wd/hub/session/:sessionId?/element/:elementId?/elements', controller.findElementsFromElement); + rest.post('/wd/hub/session/:sessionId?/touch/click', controller.doClick); + rest.post('/wd/hub/session/:sessionId?/touch/longclick', controller.touchLongClick); rest.post('/wd/hub/session/:sessionId/touch/flick', controller.pickAFlickMethod); rest.post('/wd/hub/session/:sessionId?/url', controller.postUrl); rest.get('/wd/hub/session/:sessionId?/url', controller.getUrl); @@ -181,13 +183,11 @@ var routeNotYetImplemented = function(rest) { rest.post('/wd/hub/session/:sessionId?/buttondown', controller.notYetImplemented); rest.post('/wd/hub/session/:sessionId?/buttonup', controller.notYetImplemented); rest.post('/wd/hub/session/:sessionId?/doubleclick', controller.notYetImplemented); - rest.post('/wd/hub/session/:sessionId?/touch/click', controller.doClick); rest.post('/wd/hub/session/:sessionId?/touch/down', controller.notYetImplemented); rest.post('/wd/hub/session/:sessionId?/touch/up', controller.notYetImplemented); rest.post('/wd/hub/session/:sessionId?/touch/move', controller.notYetImplemented); rest.post('/wd/hub/session/:sessionId?/touch/scroll', controller.notYetImplemented); rest.post('/wd/hub/session/:sessionId?/touch/doubleclick', controller.notYetImplemented); - rest.post('/wd/hub/session/:sessionId?/touch/longclick', controller.notYetImplemented); rest.get('/wd/hub/session/:sessionId?/location', controller.notYetImplemented); rest.get('/wd/hub/session/:sessionId?/session_storage', controller.notYetImplemented); rest.post('/wd/hub/session/:sessionId?/session_storage', controller.notYetImplemented); diff --git a/docs/touch-actions.md b/docs/touch-actions.md new file mode 100644 index 000000000..417d38b06 --- /dev/null +++ b/docs/touch-actions.md @@ -0,0 +1,19 @@ +# Touch Actions + +WebDriver provides an API to send some kinds of touch gestures to the devices, +for example, to long press an element you can do: + +```java +final WebElement imageView = searchResults.findElement(By.tagName("ImageView")); +new TouchActions(driver).longPress(imageView).perform(); +``` + +Currently Appium support some of the gestures in the Touch Actions API: + +* flick +* long press +* single tap + +Some other gestures are supported through the "Alternative access method" +explained in [Automating mobile gestures](gestures.md) +