基于 appium,在设计修改 android/ios 的自动化测试用例时,
特别烦的就是在 debug 时,需要创建 driver,就是要建立 session,而建立 session 的动作占时又比较长,所以效率比较低
之前也尝试过使用 java 动态加载的方法去模拟,将测试用例或步骤放在文本文件中读取:https://testerhome.com/topics/9040
还是觉得不爽,在脚本文件中没有语法提示
如果可以不用建立 session,直接发请求,手机处理再给结果就行了,所以我想去 session,
由此带来的问题:需要自己再写一个类似 java-client 的东西,去发这个请求
private void registerPostHandler() {
register(postHandler, new FindElement("/wd/hub/element"));
register(postHandler, new FindElements("/wd/hub/elements"));
register(postHandler, new Click("/wd/hub/element/:id/click"));
register(postHandler, new Click("/wd/hub/appium/tap"));
register(postHandler, new Clear("/wd/hub/element/:id/clear"));
register(postHandler, new RotateScreen("/wd/hub/orientation"));
register(postHandler, new RotateScreen("/wd/hub/rotation"));
register(postHandler, new PressBack("/wd/hub/back"));
register(postHandler, new SendKeysToElement("/wd/hub/element/:id/value"));
register(postHandler, new SendKeysToElement("/wd/hub/keys"));
register(postHandler, new Swipe("/wd/hub/touch/perform"));
register(postHandler, new TouchLongClick("/wd/hub/touch/longclick"));
register(postHandler, new OpenNotification("/wd/hub/appium/device/open_notifications"));
register(postHandler, new PressKeyCode("/wd/hub/appium/device/press_keycode"));
register(postHandler, new LongPressKeyCode("/wd/hub/appium/device/long_press_keycode"));
register(postHandler, new Drag("/wd/hub/touch/drag"));
register(postHandler, new AppStrings("/wd/hub/appium/app/strings"));
register(postHandler, new Flick("/wd/hub/touch/flick"));
register(postHandler, new ScrollTo("/wd/hub/touch/scroll"));
register(postHandler, new MultiPointerGesture("/wd/hub/touch/multi/perform"));
register(postHandler, new TouchDown("/wd/hub/touch/down"));
register(postHandler, new TouchUp("/wd/hub/touch/up"));
register(postHandler, new TouchMove("/wd/hub/touch/move"));
register(postHandler, new UpdateSettings("/wd/hub/appium/settings"));
register(postHandler, new NetworkConnection("/wd/hub/network_connection"));
register(postHandler, new InstallApp("/wd/hub/install_app")); //增加安装app的请求,可以通过传输ftp地址或http地址来让手机自动安装应用
register(postHandler, new ExecShell("/wd/hub/exec")); //增加执行shell命令的请求
}
private void registerGetHandler() {
register(getHandler, new Status("/wd/hub/status"));
register(getHandler, new CaptureScreenshot("/wd/hub/screenshot")); //修改截屏请求,通过base64转码成string(appium原来是存在手机的sd卡中,再导出来)
register(getHandler, new GetScreenOrientation("/wd/hub/orientation"));
register(getHandler, new GetRotation("/wd/hub/rotation"));
register(getHandler, new GetText("/wd/hub/element/:id/text"));
register(getHandler, new GetElementAttribute("/wd/hub/element/:id/attribute/:name"));
register(getHandler, new GetSize("/wd/hub/element/:id/size"));
register(getHandler, new GetName("/wd/hub/element/:id/name"));
register(getHandler, new Location("/wd/hub/element/:id/location")); //修改获取location的代码,可以获取left, right, top, bottom
register(getHandler, new GetDeviceSize("/wd/hub/window/:windowHandle/size"));
register(getHandler, new Source("/wd/hub/source")); //修改获取source的源码,去掉替换node为classname的动作
register(getHandler, new Timer("/wd/hub/timer")); //增加获取手机时间的请求(appium原来的方式是通过adb命令获取的时间,默认格式,需要自己转化后使用)
register(getHandler, new LaunchApp("/wd/hub/package/:pkg/launch")); //增加打开应用的请求
}
因为是改代码,其实也简单,把 sessionid 都去掉,注册,取消 session 的动作都不要,就变成一个 rest 服务样子的
通过查看源码,发现在单元测试中即有此部分实现,直接复制出来,改改即可生成一个 java-client,主要依赖:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.9.0</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20171018</version>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
</dependency>
@Slf4j
public abstract class HttpUtils {
public static final MediaType JSON = MediaType.parse("application/json; " + "charset=utf-8");
private static final OkHttpClient client = new OkHttpClient();
static {
final int timeout = 15 * 1000;
client.newBuilder()
.connectTimeout(timeout, SECONDS)
.readTimeout(timeout, SECONDS)
.writeTimeout(timeout, SECONDS)
.build();
}
public static String get(final String path) {
log.debug("get:" + path);
Request request = new Request.Builder().url(path).build();
return execute(request);
}
public static String post(final String path, String body) {
Request request = new Request.Builder().url(path).post(RequestBody.create(JSON, body)).build();
log.debug("POST: path:" + path + ", body:" + body);
return execute(request);
}
private static String execute(Request request) {
String result;
try {
Response response = client.newCall(request).execute();
result = response.body().string();
} catch (IOException e) {
throw new RuntimeException(request.method() + " \"" + request.url() + "\" failed. ", e);
}
if (!request.url().toString().endsWith("screenshot")) {
log.debug("execute result:" + result);
}
return result;
}
/**
* return JSONObjects count in a JSONArray
* @param jsonArray
* @return
*/
public static int getJsonObjectCountInJsonArray(JSONArray jsonArray) {
int count = 0;
try {
for (int i = 0; i < jsonArray.length(); i++, count++) {
jsonArray.getJSONObject(i);
}
return count;
} catch (JSONException e) {
return count;
}
}
@Slf4j
public class TestUtil {
/**
* finds the element using By selector
*
* @param by
* @return
*/
public static String findElement(By by) {
JSONObject json = new JSONObject();
json = getJSon(by, json);
String result = post(UI2_SERVER_ADDR + "/element", json.toString());
log.debug("findElement: " + result);
return result;
}
/**
* waits for the element for specific time
*
* @param by
* @param time
* @return
*/
public static boolean waitForElement(By by, int time) {
JSONObject jsonBody = new JSONObject();
jsonBody = getJSon(by, jsonBody);
long start = System.currentTimeMillis();
boolean foundStatus = false;
JSONObject jsonResponse;
do {
try {
String response = post(UI2_SERVER_ADDR + "/element", jsonBody.toString());
jsonResponse = new JSONObject(response);
if (jsonResponse.getInt("status") == WDStatus.SUCCESS.code()) {
foundStatus = true;
break;
}
} catch (JSONException e) {
// Retrying for element for given time
log.error("Waiting for the element ..");
}
} while (!foundStatus && ((System.currentTimeMillis() - start) <= time));
return foundStatus;
}
/**
* waits for the element to invisible for specific time
*
* @param by
* @param time
* @return
*/
public static boolean waitForElementInvisible(By by, int time) {
JSONObject jsonBody = new JSONObject();
jsonBody = getJSon(by, jsonBody);
long start = System.currentTimeMillis();
boolean foundStatus = true;
JSONObject jsonResponse;
do {
try {
String response = post(UI2_SERVER_ADDR + "/element", jsonBody.toString());
jsonResponse = new JSONObject(response);
if (jsonResponse.getInt("status") != WDStatus.SUCCESS.code()) {
foundStatus = false;
break;
}
} catch (JSONException e) {
// Retrying for element for given time
log.error("Waiting for the element ..");
}
} while (foundStatus && ((System.currentTimeMillis() - start) <= time));
return foundStatus;
}
public static List<String> findElements(By by) {
JSONObject json = new JSONObject();
json = getJSon(by, json);
String response = post(UI2_SERVER_ADDR + "/elements", json.toString());
JSONArray elements = new JSONObject(response).getJSONArray("value");
List<String> list = new ArrayList<>();
for (int i=0; i<elements.length(); i++) {
list.add(elements.getJSONObject(i).toString());
}
return list;
}
/**
* performs click on the given element
*
* @param element
* @return
* @throws JSONException
*/
public static String click(String element) throws JSONException {
String elementId = getElementId(element);
JSONObject jsonObject = new JSONObject();
jsonObject.put("element", elementId);
return post(UI2_SERVER_ADDR + "/element/" + elementId + "/click", jsonObject.toString());
}
public static String tap(int x, int y) throws JSONException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("x", x);
jsonObject.put("y", y);
return post(UI2_SERVER_ADDR + "/appium/tap", jsonObject.toString());
}
/**
* Send Keys to the element
*
* @param element
* @param text
* @return
* @throws JSONException
*/
public static String sendKeys(String element, String text) throws JSONException {
String elementId = getElementId(element);
JSONObject jsonObject = new JSONObject();
jsonObject.put("element", elementId);
jsonObject.put("text", text);
jsonObject.put("replace", false);
return post(UI2_SERVER_ADDR + "/element/" + elementId + "/value", jsonObject.toString());
}
public static String getStringValueInJsonObject(String element, String key) throws JSONException {
return new JSONObject(element).getString(key);
}
public static Object getValueInJsonObject(String jsonString, String key) throws JSONException {
return new JSONObject(jsonString).get(key);
}
/**
* get the text from the element
*
* @param element
* @return
* @throws JSONException
*/
public static String getText(String element) throws JSONException {
return getStringValueInJsonObject(get(UI2_SERVER_ADDR + "/element/" + getElementId(element) + "/text"), "value");
}
/**
* get the text from the element
*
* @param element
* @param response
*
* @return
*
* @throws JSONException
*/
// public static Response getText(String element, Response response) throws JSONException {
// String elementId = new JSONObject(element).getJSONObject("value").getString("ELEMENT");
// response = get(UI2_SERVER_ADDR + "/element/" + elementId + "/text", response);
// return response;
// }
/**
* returns the Attribute of element
*
* @param element
* @param attribute
* @return
* @throws JSONException
*/
public static String getAttribute(String element, String attribute) throws JSONException {
return get(UI2_SERVER_ADDR + "/element/" + getElementId(element) + "/attribute/" + attribute);
}
/**
* get the content-desc from the element
*
* @param element
* @return
* @throws JSONException
*/
public static String getName(String element) throws JSONException {
String response = get(UI2_SERVER_ADDR + "/element/" + getElementId(element) + "/name");
log.debug("Element name response:" + response);
return response;
}
/**
* Finds the height and width of element
*
* @param element
* @return
* @throws JSONException
*/
public static String getSize(String element) throws JSONException {
String response = get(UI2_SERVER_ADDR + "/element/" + getElementId(element) + "/size");
log.debug("Element Size response:" + response);
return response;
}
/**
* Finds the height and width of screen
*
* @return
* @throws JSONException
*/
public static Dimension getDeviceSize() throws JSONException {
String response = get(UI2_SERVER_ADDR + "/window/current/size");
log.debug("Device window Size response:" + response);
Integer height = JsonPath.compile("$.value.height").read(response);
Integer width = JsonPath.compile("$.value.width").read(response);
return new Dimension(width, height);
}
public static ElementCoordinate getElementCoordinate(String element) {
String response = getLocation(element);
Integer left = JsonPath.compile("$.value.left").read(response);
Integer right = JsonPath.compile("$.value.right").read(response);
Integer top = JsonPath.compile("$.value.top").read(response);
Integer bottom = JsonPath.compile("$.value.bottom").read(response);
return new ElementCoordinate(left, top, right, bottom);
}
public static String execShell(String cmd) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("command", cmd);
return post(UI2_SERVER_ADDR + "/exec", jsonObject.toString());
}
public static void pressBack() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("command", "input keyevent BACK");
post(UI2_SERVER_ADDR + "/exec", jsonObject.toString());
}
/**
* Flick on the give element
*
* @param element
* @return
* @throws JSONException
*/
public static String flickOnElement(String element) throws JSONException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("element", getElementId(element));
jsonObject.put("xoffset", 1);
jsonObject.put("yoffset", 1);
jsonObject.put("speed", 1000);
String response = post(UI2_SERVER_ADDR + "/touch/flick", jsonObject.toString());
log.debug("Flick on element response:" + response);
return response;
}
/**
* Flick on given position
*
* @return
* @throws JSONException
*/
public static String flickOnPosition() throws JSONException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("xSpeed", 50);
jsonObject.put("ySpeed", -180);
String response = post(UI2_SERVER_ADDR + "/touch/flick", jsonObject.toString());
log.debug("Flick response:" + response);
return response;
}
/**
* prepares the JSON Object
*
* @param by
* @param jsonObject
* @return
*/
public static JSONObject getJSon(By by, JSONObject jsonObject) {
try {
jsonObject.put("context", "");
if (by instanceof By.ByAccessibilityId) {
jsonObject.put("strategy", "accessibility id");
jsonObject.put("selector", ((By.ByAccessibilityId) by).getElementLocator());
} else if (by instanceof By.ByClass) {
jsonObject.put("strategy", "class name");
jsonObject.put("selector", ((By.ByClass) by).getElementLocator());
} else if (by instanceof By.ById) {
jsonObject.put("strategy", "id");
jsonObject.put("selector", ((By.ById) by).getElementLocator());
} else if (by instanceof By.ByXPath) {
jsonObject.put("strategy", "xpath");
jsonObject.put("selector", ((By.ByXPath) by).getElementLocator());
} else if (by instanceof By.ByAndroidUiAutomator) {
jsonObject.put("strategy", "-android uiautomator");
jsonObject.put("selector", ((By.ByAndroidUiAutomator) by).getElementLocator());
} else {
throw new JSONException("Unable to create json object: " + by);
}
} catch (JSONException e) {
log.error("Unable to form JSON Object: " + e);
}
return jsonObject;
}
/**
* prepares the JSON Object
*
* @param by
* @param contextId
* @param jsonObject
* @return
*/
public static JSONObject getJSon(By by, String contextId, JSONObject jsonObject) {
try {
jsonObject = getJSon(by, jsonObject);
jsonObject.put("context", contextId);
return jsonObject;
} catch (JSONException e) {
log.error("Unable to form JSON Object: " + e);
}
return jsonObject;
}
/**
*
* @param packageName
* @param activity
* @throws InterruptedException
*/
public static void startActivity(String packageName, String activity) throws InterruptedException {
get(UI2_SERVER_ADDR + "/wd/hub/package/"+ packageName +"/launch");
}
private static String getElementId(String element) {
if (element.contains("value")) {
return new JSONObject(element).getJSONObject("value").getString("ELEMENT");
} else {
return new JSONObject(element).getString("ELEMENT");
}
}
/**
* return the element location on the screen
*
* @param element
* @return
* @throws JSONException
*/
public static String getLocation(String element) throws JSONException {
return get(UI2_SERVER_ADDR + "/element/" + getElementId(element) + "/location");
}
/**
* performs swipe on the device screen
*
* @return
* @throws JSONException
*/
public static String swipe(int x1, int y1, int x2, int y2, int steps) throws JSONException {
// swipe from (x1,y1) to (x2,y2)
JSONObject swipeOpts = new JSONObject();
swipeOpts.put("startX", x1);
swipeOpts.put("startY", y1);
swipeOpts.put("endX", x2);
swipeOpts.put("endY", y2);
swipeOpts.put("steps", steps);
return post(UI2_SERVER_ADDR + "/touch/perform", swipeOpts.toString());
}
public static String touchDown(String element) throws JSONException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("params", getJsonForElement(element).toString());
return post(UI2_SERVER_ADDR + "/touch/down", jsonObject.toString());
}
public static String touchUp(String element) throws JSONException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("params", getJsonForElement(element).toString());
return post(UI2_SERVER_ADDR + "/touch/up", jsonObject.toString());
}
public static String touchMove(String element) throws JSONException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("params", getJsonForElement(element).toString());
return post(UI2_SERVER_ADDR + "/touch/move", jsonObject.toString());
}
public static JSONObject getJsonForElement(String elementResponse) throws JSONException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("element", getElementId(elementResponse));
return jsonObject;
}
public static boolean isElementPresent(String elementResponse) throws JSONException {
int status = new JSONObject(elementResponse).getInt("status");
return status == 0;
}
/**
* performs long click on the given element
*
* @param element
* @return
* @throws JSONException
*/
public static String longClick(String element) throws JSONException {
String elementId;
JSONObject longClickJSON = new JSONObject();
JSONObject jsonObject = new JSONObject();
try {
longClickJSON.put("params", jsonObject.put("element", getElementId(element)).put("duration",1000));
} catch (JSONException e) {
throw new RuntimeException("Element not found", e);
}
return post(UI2_SERVER_ADDR + "/touch/longclick", longClickJSON.toString());
}
/**
* perfroms scroll to the given text
*
* @param scrollToText
* @return
* @throws JSONException
*/
public static String scrollTo(String scrollToText) throws JSONException {
// TODO Create JSON object instead of below json string.Once the json is finalised from driver module
String json = " {\"cmd\":\"action\",\"action\":\"find\",\"params\":{\"strategy\":\"-android uiautomator\",\"selector\":\"" +
"new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().descriptionContains(\\\"" + scrollToText + "\\\").instance(0));" +
"new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().textContains(\\\"" + scrollToText + "\\\").instance(0));" +
"\",\"context\":\"\",\"multiple\":false}}";
JSONObject jsonObject = new JSONObject(json);
return post(UI2_SERVER_ADDR + "/touch/scroll", jsonObject.toString());
}
/**
* return the appStrings
*
* @return
* @throws JSONException
*/
public static String appStrings() throws JSONException {
JSONObject jsonObject = new JSONObject();
return post(UI2_SERVER_ADDR + "/appium/app/strings", jsonObject.toString());
}
/**
* performs screen rotation
*
* @return
* @throws JSONException
*/
public static String rotateScreen(String orientation) throws JSONException {
JSONObject postBody = new JSONObject().put("orientation", orientation);
return post(UI2_SERVER_ADDR + "/orientation", postBody.toString());
}
/**
* return screen orientation
*
* @return
* @throws JSONException
*/
public static String getScreenOrientation() throws JSONException {
String response = get(UI2_SERVER_ADDR + "/orientation");
return new JSONObject(response).get("value").toString();
}
/**
* return rotation
*
* @return
* @throws JSONException
*/
public static JSONObject getRotation() throws JSONException {
String response = get(UI2_SERVER_ADDR + "/rotation");
return new JSONObject(response).getJSONObject("value");
}
/**
* return rotation
*
* @return
* @throws JSONException
*/
public static String setRotation(JSONObject rotateMap) throws JSONException {
return post(UI2_SERVER_ADDR + "/rotation", rotateMap.toString());
}
public static String multiPointerGesture(String body) {
return post(UI2_SERVER_ADDR + "/touch/multi/perform", body);
}
public static String drag(String dragBody) throws JSONException {
return post(UI2_SERVER_ADDR + "/touch/drag", dragBody);
}
public static String captureScreenShot() {
return getStringValueInJsonObject(get(UI2_SERVER_ADDR + "/screenshot"), "value");
}
public static String getDeviceTime() {
return getStringValueInJsonObject(get(UI2_SERVER_ADDR + "/timer"), "value");
}
终于不用每次调试的时候去创建 driver 了,随便打开某个页面,都可以调试了