Merge pull request #668 from bootstraponline/find_by_id

Add find element by id for Android
This commit is contained in:
bootstraponline
2013-06-03 08:19:21 -07:00
6 changed files with 129 additions and 53 deletions
+52 -10
View File
@@ -396,19 +396,41 @@ ADB.prototype.prepareDevice = function(onReady) {
], onReady);
};
ADB.prototype.pushStrings = function(cb) {
var me = this;
var stringsFromApkJarPath = path.resolve(__dirname, '../app/android/strings_from_apk.jar');
var outputPath = path.resolve(getTempPath(), me.appPackage);
var makeStrings = ['java -jar ', stringsFromApkJarPath,
' ', me.apkPath, ' ', outputPath].join('');
logger.debug(makeStrings);
exec(makeStrings, {}, function(err, stdout, stderr) {
if (err) {
logger.debug(stderr);
return cb("error making strings");
}
var jsonFile = path.resolve(outputPath, 'strings.json');
var remotePath = "/data/local/tmp";
var pushCmd = me.adbCmd + " push " + jsonFile + " " + remotePath;
exec(pushCmd, {}, function(err, stdout, stderr) {
cb(null);
});
});
};
ADB.prototype.startAppium = function(onReady, onExit) {
logger.info("Starting android appium");
var me = this
, doRun = function(err) {
if (err) return onReady(err);
me.runBootstrap(onReady, onExit);
};
if (err) return onReady(err);
me.runBootstrap(onReady, onExit);
};
this.onExit = onExit;
logger.debug("Using fast reset? " + this.fastReset);
async.series([
function(cb) { me.prepareDevice(cb); },
function(cb) { me.pushStrings(cb); },
function(cb) { me.installApp(cb); },
function(cb) { me.forwardPort(cb); },
function(cb) { me.pushAppium(cb); },
@@ -701,11 +723,9 @@ ADB.prototype.runBootstrap = function(readyCb, exitCb) {
// The bootstrap jar has crashed so it must be restarted.
this.restartBootstrap = false;
me.runBootstrap(function() {
readyCb(null, function() {
// Resend last command because the client is still waiting for the
// response.
me.android.push(null, true);
});
// Resend last command because the client is still waiting for the
// response.
me.android.push(null, true);
}, exitCb);
return;
}
@@ -761,7 +781,25 @@ ADB.prototype.sendAutomatorCommand = function(action, params, cb) {
};
ADB.prototype.sendCommand = function(type, extra, cb) {
if (this.socketClient) {
if (this.cmdCb !== null) {
logger.warn("Trying to run a command when one is already in progress. " +
"Will spin a bit and try again");
var me = this;
var start = Date.now();
var timeoutMs = 10000;
var intMs = 200;
var waitForCmdCbNull = function() {
if (me.cmdCb === null) {
me.sendCommand(type, extra, cb);
} else if ((Date.now() - start) < timeoutMs) {
setTimeout(waitForCmdCbNull, intMs);
} else {
cb(new Error("Never became able to push strings since a command " +
"was in process"));
}
};
waitForCmdCbNull();
} else if (this.socketClient) {
if (typeof extra === "undefined" || extra === null) {
extra = {};
}
@@ -769,7 +807,11 @@ ADB.prototype.sendCommand = function(type, extra, cb) {
cmd = _.extend(cmd, extra);
var cmdJson = JSON.stringify(cmd) + "\n";
this.cmdCb = cb;
this.debug("Sending command to android: " + cmdJson.trim());
var logCmd = cmdJson.trim();
if (logCmd.length > 1000) {
logCmd = logCmd.substr(0, 1000) + "...";
}
this.debug("Sending command to android: " + logCmd);
this.socketClient.write(cmdJson);
} else {
cb({
@@ -1,18 +1,25 @@
package io.appium.android.bootstrap;
import org.json.JSONException;
import io.appium.android.bootstrap.exceptions.AndroidCommandException;
import io.appium.android.bootstrap.exceptions.CommandTypeException;
import io.appium.android.bootstrap.exceptions.SocketServerException;
import io.appium.android.bootstrap.handler.Find;
import io.appium.android.bootstrap.utils.TheWatchers;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Timer;
import java.util.TimerTask;
import io.appium.android.bootstrap.exceptions.AndroidCommandException;
import io.appium.android.bootstrap.exceptions.CommandTypeException;
import io.appium.android.bootstrap.exceptions.SocketServerException;
import io.appium.android.bootstrap.utils.TheWatchers;
import org.json.JSONException;
import org.json.JSONObject;
/**
* The SocketServer class listens on a specific port for commands from Appium,
@@ -27,7 +34,8 @@ class SocketServer {
PrintWriter out;
boolean keepListening;
private final AndroidCommandExecutor executor;
private final TheWatchers watchers = TheWatchers.getInstance();
private final TheWatchers watchers = TheWatchers.getInstance();
private final Timer timer = new Timer("WatchTimer");
/**
* Constructor
@@ -45,6 +53,7 @@ class SocketServer {
throw new SocketServerException(
"Could not start socket server listening on " + port);
}
}
/**
@@ -104,17 +113,22 @@ class SocketServer {
*/
public void listenForever() throws SocketServerException {
Logger.info("Appium Socket Server Ready");
loadStringsJson();
final TimerTask updateWatchers = new TimerTask() {
@Override
public void run() {
watchers.check();
}
};
timer.scheduleAtFixedRate(updateWatchers, 100, 100);
try {
client = server.accept();
Logger.info("Client connected");
in = new BufferedReader(new InputStreamReader(client.getInputStream()));
out = new PrintWriter(client.getOutputStream(), true);
while (keepListening) {
if (in.ready()) {
handleClientData();
}
Thread.sleep(100);
watchers.check();
handleClientData();
}
in.close();
out.close();
@@ -122,8 +136,24 @@ class SocketServer {
Logger.info("Closed client connection");
} catch (final IOException e) {
throw new SocketServerException("Error when client was trying to connect");
} catch (InterruptedException e) {
throw new SocketServerException("The socket server was interupted");
}
}
public void loadStringsJson() {
Logger.info("Loading json...");
try {
final File jsonFile = new File("/data/local/tmp/strings.json");
final DataInputStream dataInput = new DataInputStream(
new FileInputStream(jsonFile));
final byte[] jsonBytes = new byte[(int) jsonFile.length()];
dataInput.readFully(jsonBytes);
// this closes FileInputStream
dataInput.close();
final String jsonString = new String(jsonBytes, "UTF-8");
Find.apkStrings = new JSONObject(jsonString);
Logger.info("json loading complete.");
} catch (final Exception e) {
e.printStackTrace();
}
}
@@ -131,7 +161,8 @@ class SocketServer {
* When {@link #handleClientData()} has valid data, this method delegates the
* command.
*
* @param cmd AndroidCommand
* @param cmd
* AndroidCommand
* @return Result
*/
private String runCommand(final AndroidCommand cmd) {
@@ -16,6 +16,8 @@ import io.appium.android.bootstrap.exceptions.InvalidStrategyException;
import io.appium.android.bootstrap.exceptions.UnallowedTagNameException;
import io.appium.android.bootstrap.selector.Strategy;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
@@ -37,8 +39,9 @@ import com.android.uiautomator.core.UiSelector;
*
*/
public class Find extends CommandHandler {
AndroidElementsHash elements = AndroidElementsHash.getInstance();
Dynamic dynamic = new Dynamic();
AndroidElementsHash elements = AndroidElementsHash.getInstance();
Dynamic dynamic = new Dynamic();
public static JSONObject apkStrings = null;
private Object[] cascadeChildSels(final ArrayList<UiSelector> tail,
final ArrayList<String> tailOuts) {
@@ -324,8 +327,8 @@ public class Find extends CommandHandler {
* @throws InvalidStrategyException
* @throws AndroidCommandException
*/
private List<UiSelector> getSelector(final Strategy strategy,
final String text, final Boolean many) throws InvalidStrategyException,
private List<UiSelector> getSelector(final Strategy strategy, String text,
final Boolean many) throws InvalidStrategyException,
AndroidCommandException, UnallowedTagNameException {
final List<UiSelector> selectors = new ArrayList<UiSelector>();
UiSelector sel = new UiSelector();
@@ -349,6 +352,19 @@ public class Find extends CommandHandler {
selectors.add(sel2);
}
break;
case ID:
try {
text = apkStrings.getString(text);
Logger.debug("Searching for text: " + text);
} catch (final Exception e) { // JSONException and NullPointerException
final StringWriter string = new StringWriter();
e.printStackTrace(new PrintWriter(string));
throw new InvalidStrategyException("Unable to search by ID for "
+ text + ".\n" + string.toString());
}
// now fall through and do a name search
case NAME:
sel = sel.description(text);
if (!many) {
@@ -367,7 +383,6 @@ public class Find extends CommandHandler {
break;
case LINK_TEXT:
case PARTIAL_LINK_TEXT:
case ID:
case CSS_SELECTOR:
default:
throw new InvalidStrategyException("Strategy "
@@ -7,38 +7,26 @@ import io.appium.android.bootstrap.Logger;
public class TheWatchers {
private static TheWatchers ourInstance = new TheWatchers();
private long start = System.currentTimeMillis();
private long delta = 1;
private boolean alerted = false;
public static TheWatchers getInstance() {
return ourInstance;
}
private TheWatchers() {
start = System.currentTimeMillis();
}
public void setDelta(long seconds) {
delta = seconds * 1000;
}
private TheWatchers() { }
public boolean check() {
if(start + delta < System.currentTimeMillis()) {
// Send only one alert message...
if (isDialogPresent() && (!alerted)) {
Logger.info("Emitting system alert message");
alerted = true;
}
// if the dialog went away, make sure we can send an alert again
if (!isDialogPresent() && alerted) {
alerted = false;
}
start = System.currentTimeMillis();
// Send only one alert message...
if (isDialogPresent() && (!alerted)) {
Logger.info("Emitting system alert message");
alerted = true;
}
return false;
// if the dialog went away, make sure we can send an alert again
if (!isDialogPresent() && alerted) {
alerted = false;
}
return alerted;
}
public boolean isDialogPresent() {
+1 -1
View File
@@ -792,4 +792,4 @@ Android.prototype.getCurrentActivity = function(cb) {
module.exports = function(opts) {
return new Android(opts);
};
};
Binary file not shown.