From d2282486af0d60996befc56ef0e97baf8c107bea Mon Sep 17 00:00:00 2001 From: Eric Plaster Date: Tue, 2 Jul 2013 17:09:14 -0500 Subject: [PATCH] Updated Exception handling in scrollto for android --- .../bootstrap/AndroidCommandExecutor.java | 2 + .../android/bootstrap/AndroidElement.java | 4 + .../android/bootstrap/handler/ScrollTo.java | 81 +++++++++++++++++++ app/android.js | 11 ++- app/controller.js | 10 ++- app/ios.js | 3 +- test/functional/apidemos/gestures.js | 33 ++++++++ 7 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 android/bootstrap/src/io/appium/android/bootstrap/handler/ScrollTo.java diff --git a/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandExecutor.java b/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandExecutor.java index 8c8db79ed..04180eb74 100644 --- a/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandExecutor.java +++ b/android/bootstrap/src/io/appium/android/bootstrap/AndroidCommandExecutor.java @@ -4,6 +4,7 @@ import io.appium.android.bootstrap.exceptions.AndroidCommandException; import io.appium.android.bootstrap.handler.Clear; import io.appium.android.bootstrap.handler.Click; import io.appium.android.bootstrap.handler.Find; +import io.appium.android.bootstrap.handler.ScrollTo; import io.appium.android.bootstrap.handler.Flick; import io.appium.android.bootstrap.handler.GetAttribute; import io.appium.android.bootstrap.handler.GetDeviceSize; @@ -43,6 +44,7 @@ class AndroidCommandExecutor { map.put("getName", new GetName()); map.put("getAttribute", new GetAttribute()); map.put("getDeviceSize", new GetDeviceSize()); + map.put("scrollTo", new ScrollTo()); map.put("find", new Find()); map.put("getLocation", new GetLocation()); map.put("wake", new Wake()); diff --git a/android/bootstrap/src/io/appium/android/bootstrap/AndroidElement.java b/android/bootstrap/src/io/appium/android/bootstrap/AndroidElement.java index 4082f44fc..7aa614e06 100644 --- a/android/bootstrap/src/io/appium/android/bootstrap/AndroidElement.java +++ b/android/bootstrap/src/io/appium/android/bootstrap/AndroidElement.java @@ -27,6 +27,10 @@ public class AndroidElement { el = uiObj; } + public UiObject getUiObject() { + return this.el; + } + public void clearText() throws UiObjectNotFoundException { el.clearTextField(); } diff --git a/android/bootstrap/src/io/appium/android/bootstrap/handler/ScrollTo.java b/android/bootstrap/src/io/appium/android/bootstrap/handler/ScrollTo.java new file mode 100644 index 000000000..a3a5d82f1 --- /dev/null +++ b/android/bootstrap/src/io/appium/android/bootstrap/handler/ScrollTo.java @@ -0,0 +1,81 @@ +package io.appium.android.bootstrap.handler; + +import com.android.uiautomator.core.UiObject; +import com.android.uiautomator.core.UiObjectNotFoundException; +import com.android.uiautomator.core.UiScrollable; +import com.android.uiautomator.core.UiSelector; + +import org.json.JSONException; + +import java.util.Hashtable; + +import io.appium.android.bootstrap.AndroidCommand; +import io.appium.android.bootstrap.AndroidCommandResult; +import io.appium.android.bootstrap.AndroidElement; +import io.appium.android.bootstrap.CommandHandler; +import io.appium.android.bootstrap.WDStatus; +import io.appium.android.bootstrap.exceptions.ElementNotInHashException; + +/** + * This handler is used to scroll to elements in the Android UI. + * + * Based on the element Id of the scrollable, scroll to the object with the text. + * + */ +public class ScrollTo 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(final AndroidCommand command) + throws JSONException { + if (!command.isElementCommand()) { + return getErrorResult("A scrollable view is required for this command."); + } + + try { + Boolean result; + final Hashtable params = command.params(); + String text = params.get("text").toString(); + + AndroidElement el = command.getElement(); + + final UiScrollable view = new UiScrollable(el.getUiObject().getSelector()); + + view.scrollToBeginning(100); + view.setMaxSearchSwipes(100); + result = view.scrollTextIntoView(text); + view.waitForExists(5000); + + // make sure we can get to the item + UiObject listViewItem = view.getChildByText(new UiSelector() + .className(android.widget.TextView.class.getName()), ""+text+""); + + // We need to make sure that the item exists (visible) + if(!(result && listViewItem.exists())) { + return getErrorResult("Could not scroll element into view: "+text); + } + return getSuccessResult(result); + } 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 NullPointerException e) { // el is null + return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT, + e.getMessage()); + } catch (final Exception e) { + return new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, + e.getMessage()); + } + } +} diff --git a/app/android.js b/app/android.js index 40a462503..0aac63da7 100644 --- a/app/android.js +++ b/app/android.js @@ -804,8 +804,15 @@ Android.prototype.flick = function(startX, startY, endX, endY, touchCount, elId, } }; -Android.prototype.scrollTo = function(elementId, cb) { - cb(new NotYetImplementedError(), null); +Android.prototype.scrollTo = function(elementId, text, cb) { + // instead of the elementId as the element to be scrolled too, + // it's the scrollable view to swipe until the uiobject that has the + // text is found. + var opts = { + text: text + , elementId: elementId + }; + this.proxy(["element:scrollTo", opts], cb); }; Android.prototype.shake = function(cb) { diff --git a/app/controller.js b/app/controller.js index b90ac9095..d59398360 100644 --- a/app/controller.js +++ b/app/controller.js @@ -471,8 +471,14 @@ exports.mobileSwipe = function(req, res) { }; exports.mobileScrollTo = function(req, res) { - var elementId = req.body.element; - req.device.scrollTo(elementId, getResponseHandler(req, res)); + req.body = _.defaults(req.body, { + element: null + , text: null + }); + var element = req.body.element + , text = req.body.text; + + req.device.scrollTo(element, text, getResponseHandler(req, res)); }; exports.mobileShake = function(req, res) { diff --git a/app/ios.js b/app/ios.js index c476e979e..f91e9e1cf 100644 --- a/app/ios.js +++ b/app/ios.js @@ -1509,7 +1509,8 @@ IOS.prototype.flick = function(startX, startY, endX, endY, touchCount, elId, cb) this.proxy(command, cb); }; -IOS.prototype.scrollTo = function(elementId, cb) { +IOS.prototype.scrollTo = function(elementId, text, cb) { + // we ignore text for iOS, as the element is the one being scrolled too var command = ["au.getElement('", elementId, "').scrollToVisible()"].join(''); this.proxy(command, cb); }; diff --git a/test/functional/apidemos/gestures.js b/test/functional/apidemos/gestures.js index e19a6a2ca..644f5e17d 100644 --- a/test/functional/apidemos/gestures.js +++ b/test/functional/apidemos/gestures.js @@ -119,3 +119,36 @@ describeWd('gestures', function(h) { }); }); }); + +describeWd('scroll to element', function(h) { + it('should bring the element into view', function(done) { + h.driver.elementsByTagName('textView', function(err, els) { + should.not.exist(err); + var el = els[els.length-1]; + el.click(function(err) { + should.not.exist(err); + h.driver.elementByTagName('listView', function(err, el) { + should.not.exist(err); + var scrollOpts = { + element: el.value + , text: 'Switches' + }; + var next = function() { + h.driver.execute("mobile: scrollTo", [scrollOpts], function(err) { + should.not.exist(err); + h.driver.elementByName('Switches', function(err, el) { + should.not.exist(err); + el.getAttribute('name', function(err, text) { + should.not.exist(err); + text.should.equal("Switches"); + done(); + }); + }); + }); + }; + setTimeout(next, 3000); + }); + }); + }); + }); +});