Merge pull request #2939 from bootstraponline/scroll_into_view

Add scrollIntoView
This commit is contained in:
bootstraponline
2014-06-25 14:02:12 -04:00
5 changed files with 75 additions and 4 deletions

View File

@@ -0,0 +1,37 @@
# uiautomator UiSelector
Appium enables searching using [UiSelectors](http://developer.android.com/tools/help/uiautomator/UiSelector.html).
[UiScrollable](http://developer.android.com/tools/help/uiautomator/UiScrollable.html)
is also supported.
Note that the index selector is unreliable so prefer instance instead. The
following examples are written against the api demos apk using Ruby.
Find the first textview.
```ruby
first_textview = find_element(:uiautomator, 'new UiSelector().className("android.widget.TextView").instance(0)');
```
Find the first element by text.
```ruby
first_text = find_element(:uiautomator, 'new UiSelector().text("Animation")')
first_text.text # "Animation"
```
Find the first scrollable element, then find a TextView with the text "Tabs".
The "Tabs" element will be scrolled into view.
```ruby
element = find_element(:uiautomator, 'new UiScrollable(new UiSelector().scrollable(true).instance(0)).getChildByText(new UiSelector().className("android.widget.TextView"), "Tabs")')
```
As a special case, scrollIntoView returns the element that is scrolled into view.
scrollIntoView allows scrolling to any UiSelector.
```ruby
element = find_element(:uiautomator, 'new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text("WebView").instance(0));')
element.text # "WebView"
```

View File

@@ -1,6 +1,7 @@
package io.appium.android.bootstrap.utils;
import com.android.uiautomator.core.UiSelector;
import io.appium.android.bootstrap.Logger;
import io.appium.android.bootstrap.exceptions.UiSelectorSyntaxException;
import java.util.ArrayList;
@@ -77,8 +78,10 @@ public class UiAutomatorParser {
statement = text.substring(0, index);
if (UiScrollableParser.isUiScrollable(statement)) {
Logger.debug("Parsing scrollable: " + statement);
selectors.add(scrollableParser.parse(statement));
} else {
Logger.debug("Parsing selector: " + statement);
selectors.add(selectorParser.parse(statement));
}

View File

@@ -3,6 +3,7 @@ package io.appium.android.bootstrap.utils;
import com.android.uiautomator.core.UiObject;
import com.android.uiautomator.core.UiScrollable;
import com.android.uiautomator.core.UiSelector;
import io.appium.android.bootstrap.Logger;
import io.appium.android.bootstrap.exceptions.UiSelectorSyntaxException;
import java.lang.reflect.InvocationTargetException;
@@ -183,6 +184,12 @@ public class UiScrollableParser {
}
private void applyArgsToMethod(Method method, ArrayList<String> arguments) throws UiSelectorSyntaxException {
StringBuilder sb = new StringBuilder();
for (String arg : arguments) {
sb.append(arg + ", ");
}
Logger.debug("UiScrollable invoking method: " + method + " args: " + sb.toString());
if (method.getGenericReturnType() == UiScrollable.class && returnedUiObject) {
throw new UiSelectorSyntaxException("Cannot call UiScrollable method \"" + method.getName() + "\" on a UiObject instance");
}
@@ -208,17 +215,31 @@ public class UiScrollableParser {
convertedArgs.add(coerceArgToType(parameterTypes[i], arguments.get(i)));
}
if (method.getGenericReturnType() == UiScrollable.class) {
String methodName = method.getName();
Logger.debug("Method name: " + methodName);
boolean scrollIntoView = methodName.contentEquals("scrollIntoView");
if (method.getGenericReturnType() == UiScrollable.class || scrollIntoView) {
if (convertedArgs.size() > 1) {
throw new UiSelectorSyntaxException("No UiScrollable method that returns type UiScrollable takes more than 1 argument");
}
try {
scrollable = (UiScrollable)method.invoke(scrollable, convertedArgs.get(0));
if (scrollIntoView) {
Logger.debug("Setting uiObject for scrollIntoView");
UiSelector arg = (UiSelector) convertedArgs.get(0);
returnedUiObject = true;
uiObject = new UiObject(arg);
Logger.debug("Invoking method: " + method + " with: " + uiObject);
method.invoke(scrollable, uiObject);
Logger.debug("Invoke complete.");
} else {
scrollable = (UiScrollable)method.invoke(scrollable, convertedArgs.get(0));
}
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new UiSelectorSyntaxException("problem using reflection to call this method");
} catch (InvocationTargetException e) {
e.printStackTrace();
Logger.error(e.getCause().toString()); // we're only interested in the cause. InvocationTarget wraps the underlying problem.
throw new UiSelectorSyntaxException("problem using reflection to call this method");
}
}
@@ -259,6 +280,7 @@ public class UiScrollableParser {
}
private Object coerceArgToType(Type type, String argument) throws UiSelectorSyntaxException {
Logger.debug("UiScrollable coerce type: " + type + " arg: " + argument);
if (type == boolean.class) {
if (argument.equals("true")) {
return true;
@@ -288,7 +310,7 @@ public class UiScrollableParser {
}
}
if (type == UiSelector.class) {
if (type == UiSelector.class || type == UiObject.class) {
UiSelectorParser parser = new UiSelectorParser();
return parser.parse(argument);
}

View File

@@ -1,6 +1,7 @@
package io.appium.android.bootstrap.utils;
import com.android.uiautomator.core.UiSelector;
import io.appium.android.bootstrap.Logger;
import io.appium.android.bootstrap.exceptions.UiSelectorSyntaxException;
import java.lang.reflect.InvocationTargetException;
@@ -151,6 +152,7 @@ public class UiSelectorParser {
}
private Object coerceArgToType(Type type, String argument) throws UiSelectorSyntaxException {
Logger.debug("UiSelector coerce type: " + type + " arg: " + argument);
if (type == boolean.class) {
if (argument.equals("true")) {
return true;

View File

@@ -153,6 +153,13 @@ describe("apidemo - find elements - by uiautomator", function () {
text.should.equal("Views");
}).nodeify(done);
});
it('should allow UiScrollable scrollIntoView', function (done) {
driver.elementByAndroidUIAutomator('new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text("Views").instance(0));')
.text()
.then(function (text) {
text.should.equal("Views");
}).nodeify(done);
});
it('should error reasonably if a UiScrollable does not return a UiObject', function (done) {
driver.elementByAndroidUIAutomator('new UiScrollable(new UiSelector().scrollable(true).instance(0)).setMaxSearchSwipes(10)')
.should.be.rejectedWith(/status: 9/)