Merge pull request #1945 from bootstraponline/master

Android longClick x, y, and duration support
This commit is contained in:
bootstraponline
2014-02-27 16:19:58 -05:00
5 changed files with 147 additions and 22 deletions

3
.gitignore vendored
View File

@@ -34,8 +34,7 @@ sample-code/apps/ApiDemos
*~
org.eclipse.ltk.core.refactoring.prefs
/selendroid
.appiumconfig
.appiumconfig.json
.appiumconfig*
build/
.idea
lib/devices/ios/uiauto/lib/status.js

View File

@@ -113,8 +113,13 @@ androidController.click = function (elementId, cb) {
this.proxy(["element:click", {elementId: elementId}], cb);
};
androidController.touchLongClick = function (elementId, cb) {
this.proxy(["element:touchLongClick", {elementId: elementId}], cb);
androidController.touchLongClick = function (elementId, x, y, duration, cb) {
var opts = {};
if (elementId) opts.elementId = elementId;
if (x) opts.x = x;
if (y) opts.y = y;
if (duration) opts.duration = duration;
this.proxy(["element:touchLongClick", opts], cb);
};
androidController.fireEvent = function (evt, elementId, value, cb) {

View File

@@ -10,6 +10,8 @@ import io.appium.android.bootstrap.exceptions.ElementNotInHashException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Hashtable;
import org.json.JSONException;
@@ -38,7 +40,7 @@ public class TouchLongClick extends CommandHandler {
* UiAutomator has a broken longClick. getAutomatorBridge is private so we
* access the bridge via reflection to use the touchDown / touchUp methods.
*/
private boolean correctLongClick(final int x, final int y) {
private boolean correctLongClick(final int x, final int y, final int duration) {
try {
/*
* bridge.getClass() returns ShellUiAutomatorBridge on API 18/19 so use
@@ -61,7 +63,7 @@ public class TouchLongClick extends CommandHandler {
touchUp.setAccessible(true);
if ((Boolean) touchDown.invoke(controller, x, y)) {
SystemClock.sleep(2000);
SystemClock.sleep(duration);
if ((Boolean) touchUp.invoke(controller, x, y)) {
return true;
}
@@ -85,25 +87,68 @@ public class TouchLongClick extends CommandHandler {
@Override
public AndroidCommandResult execute(final AndroidCommand command)
throws JSONException {
if (!command.isElementCommand()) {
return getErrorResult("Unable to long click without an element.");
}
try {
final AndroidElement el = command.getElement();
final Hashtable<String, Object> params = command.params();
AndroidElement el = null;
int clickX = -1;
int clickY = -1;
final Rect bounds = el.getVisibleBounds();
final int x = bounds.centerX();
final int y = bounds.centerY();
boolean isElement = false;
// isElementCommand doesn't check to see if we actually have an element
// so getElement is used instead.
try {
if (command.getElement() != null) {
isElement = true;
}
} catch (final Exception e) {
}
if (correctLongClick(x, y)) {
if (isElement) {
// extract x and y from the element.
el = command.getElement();
final Rect bounds = el.getVisibleBounds();
clickX = bounds.centerX();
clickY = bounds.centerY();
} else { // no element so extract x and y from params
final Object paramX = params.get("x");
final Object paramY = params.get("y");
double targetX = 0.5;
double targetY = 0.5;
if (paramX != null) {
targetX = Double.parseDouble(paramX.toString());
}
if (paramY != null) {
targetY = Double.parseDouble(paramY.toString());
}
final ArrayList<Integer> posVals = absPosFromCoords(new Double[] {
targetX, targetY });
clickX = posVals.get(0);
clickY = posVals.get(1);
}
final Object paramDuration = params.get("duration");
int duration = 2000; // two seconds
if (paramDuration != null) {
duration = Integer.parseInt(paramDuration.toString());
}
Logger.debug("longClick using element? " + isElement + " x: " + clickX
+ ", y: " + clickY + ", duration: " + duration);
if (correctLongClick(clickX, clickY, duration)) {
return getSuccessResult(true);
}
Logger.debug("Falling back to broken longClick");
// if correctLongClick failed and we have an element
// then uiautomator's longClick is used as a fallback.
if (isElement) {
Logger.debug("Falling back to broken longClick");
final boolean res = el.longClick();
return getSuccessResult(res);
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) {
@@ -111,5 +156,6 @@ public class TouchLongClick extends CommandHandler {
} catch (final Exception e) {
return getErrorResult(e.getMessage());
}
return getErrorResult("Failed to long click");
}
}

View File

@@ -256,8 +256,14 @@ exports.doClick = function (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));
var x = req.body.x;
var y = req.body.y;
var duration = req.body.duration;
if (element && checkMissingParams(res, {element: element}, true)) {
req.device.touchLongClick(element, x, y, duration, getResponseHandler(req, res));
} else if (checkMissingParams(res, {x: x, y: y}, true)) {
req.device.touchLongClick(element, x, y, duration, getResponseHandler(req, res));
}
};

View File

@@ -27,7 +27,7 @@ describe("apidemo - gestures -", function () {
.nodeify(done);
});
//todo: not working in nexus 7
it(' should click via x/y pct', function (done) {
it('should click via x/y pct', function (done) {
// this test depends on having a certain size screen, obviously
// I use a nexus something or other phone style thingo
driver
@@ -232,4 +232,73 @@ describe("apidemo - gestures -", function () {
}).nodeify(done);
});
});
it('should long click via element value', function (done) {
var element;
driver
.elementsByTagName("text").then(function (els) { element = els[1]; })
.then(function () { driver.execute("mobile: longClick", [{element: element.value}]); })
.sleep(3000)
.elementsByTagName("text").then(function (els) { return els[1]; }).text()
.then(function (text) {
["Accessibility Node Provider"].should.include(text);
}).nodeify(done);
});
it('should long click via element value with custom duration', function (done) {
var element;
driver
.elementsByTagName("text").then(function (els) { element = els[1]; })
.then(function () { driver.execute("mobile: longClick", [{element: element.value, duration: 1000}]); })
.sleep(3000)
.elementsByTagName("text").then(function (els) { return els[1]; }).text()
.then(function (text) {
["Accessibility Node Provider"].should.include(text);
}).nodeify(done);
});
it('should long click via pixel value', function (done) {
var element, location, elSize;
driver
.elementsByTagName("text").then(function (els) { element = els[1]; })
.then(function () { return element.getLocation(); })
.then(function (loc) { location = loc; })
.then(function () { return element.getSize(); })
.then(function (size) { elSize = size; })
.then(function () {
var centerX = location.x + (elSize.width / 2);
var centerY = location.y + (elSize.height / 2);
driver.execute("mobile: longClick", [{x: centerX, y: centerY}]);
})
.sleep(3000)
.elementsByTagName("text").then(function (els) { return els[1]; }).text()
.then(function (text) {
["Accessibility Node Provider"].should.include(text);
}).nodeify(done);
});
it('should long click via relative value', function (done) {
var element, location, elSize, windowSize;
driver
.elementsByTagName("text").then(function (els) { element = els[1]; })
.then(function () { return element.getLocation(); })
.then(function (loc) { location = loc; })
.then(function () { return element.getSize(); })
.then(function (size) { elSize = size; })
.then(function () { return driver.getWindowSize(); })
.then(function (size) { windowSize = size; })
.then(function () {
var relX = (location.x + (elSize.width / 2)) / windowSize.width;
var relY = (location.y + (elSize.height / 2)) / windowSize.height;
driver.execute("mobile: longClick", [{x: relX, y: relY}]);
})
.sleep(3000)
.elementsByTagName("text").then(function (els) { return els[1]; }).text()
.then(function (text) {
["Accessibility Node Provider"].should.include(text);
}).nodeify(done);
});
});