手机自动化测试:appium源码分析之bootstrap一

 

前言:

poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标。poptest推出手机自动化测试的课程,讲解appuim的实际应用,培训全程用商业项目,   大家可以加qq群进行交流:195983133

开源的项目最大的好处是可以获得源代码,我们可以在源代码的基础上进行修改代码,编译,开发出符合我们要求的项目。如果想要做到可以修改源代码,首先要读懂代码了解代码。国内的一些公司就是在开源的软件基础上进行了二次开发封装,然后打出自己的logo,在销售给客户。一些互联网公司也的产品代码中也用了好多开源的代码。那么我们来看看appium的代码是什么样的。

准备:

appium是开源项目,可以获得源码:appium-master,在eclipse中用maven导入出现2个项目:bootstrap和sauce_appium_junit。
 sauce_appium_junit是测试用例集合,帮助学习,如果有时间可以好好读读。bootstrap是appium运行在手机端的服务器。

开始

一.bootstrap作用
bootstrap以jar包形式存在,实际上是uiautomator写的case包,通过PC端的命令在手机端执行。

二.bootstrap源码分析
程序入口是Bootstrap类
下面是Bootstrap.java源码

package io.appium.android.bootstrap;

import io.appium.android.bootstrap.exceptions.SocketServerException;

import com.android.uiautomator.testrunner.UiAutomatorTestCase;

/**
* The Bootstrap class runs the socket server. uiautomator开发的脚本,可以直接在pc端启动
*/
public class Bootstrap extends UiAutomatorTestCase { public void testRunServer() {
SocketServer server;
try {
// 启动socket服务器,监听4724端口。
server = new SocketServer(4724);
server.listenForever();
} catch (final SocketServerException e) {
Logger.error(e.getError());
System.exit(1);
} }
} 该类是启动线程,监听4724端口,通过该端口与appium通信。该类继承自UiAutomatorTestCase。pc端通过adb发送指令adb shell uiautomator runtest AppiumBootstrap.jar -c ,io.appium.android.bootstrap.Bootstrap执行。

然后执行server.listenForever()方法
请看SocketServer.java源码

/**
* Listens on the socket for data, and calls {@link #handleClientData()} when
* it's available.
*
* @throws SocketServerException
*/
public void listenForever() throws SocketServerException {
Logger.debug("Appium Socket Server Ready");
//读取strings.json文件的数据
UpdateStrings.loadStringsJson();
// 注册两种监听器:AND和Crash
dismissCrashAlerts();
final TimerTask updateWatchers = new TimerTask() {
@Override
public void run() {
try {
// 检查系统是否有异常
watchers.check();
} catch (final Exception e) {
}
}
};
// 计时器,0.1秒后开始,每隔0.1秒执行一次。
timer.scheduleAtFixedRate(updateWatchers, 100, 100); try {
client = server.accept();
Logger.debug("Client connected");
in = new BufferedReader(new InputStreamReader(client.getInputStream(),
"UTF-8"));
out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream(),
"UTF-8"));
while (keepListening) {
// 获取客户端数据
handleClientData();
}
in.close();
out.close();
client.close();
Logger.debug("Closed client connection");
} catch (final IOException e) {
throw new SocketServerException("Error when client was trying to connect");
}
}
该方法中调用UpdateStrings.loadStringsJson();代码如下(UpdateStrings):
/**
* strings.json文件保存的是apk的strings.xml里的内容,在Bootstrap启动前由appium服务器解析并push到设备端的
*
* @return
*/
public static boolean loadStringsJson() {
Logger.debug("Loading json...");
try {
final String filePath = "/data/local/tmp/strings.json";
final File jsonFile = new File(filePath);
// json will not exist for apks that are only on device
// 你的case必须写明apk的路径,如果启动设备上已有的应用而case中没有app路径,此时json文件是不存在的
// because the node server can't extract the json from the apk.
if (!jsonFile.exists()) {
return false;
}
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类中的属性,以做后用
Find.apkStrings = new JSONObject(jsonString);
Logger.debug("json loading complete.");
} catch (final Exception e) {
Logger.error("Error loading json: " + e.getMessage());
return false;
}
return true;
}
执行后返回到ServerSocket类的listenForever(),执行到dismissCrashAlerts();该方法是注册监听器,观察是否有弹出框或者AND和crash异常。
public void dismissCrashAlerts() {
try {
new UiWatchers().registerAnrAndCrashWatchers();
Logger.debug("Registered crash watchers.");
} catch (final Exception e) {
Logger.debug("Unable to register crash watchers.");
}
}
listenForever()方法执行到注册心跳程序,每隔0.1秒开始执行一遍上面注册的监听器来检查系统是否存在异常。
final TimerTask updateWatchers = new TimerTask() {
@Override
public void run() {
try {
// 检查系统是否有异常
watchers.check();
} catch (final Exception e) {
}
}
};
// 计时器,0.1秒后开始,每隔0.1秒执行一次。
timer.scheduleAtFixedRate(updateWatchers, 100, 100);
然后启动数据通道,接受客户端发来的数据和返回结果给客户端。
client = server.accept();
Logger.debug("Client connected");
in = new BufferedReader(new InputStreamReader(client.getInputStream(),
"UTF-8"));
out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream(),
"UTF-8"));
接下来是handleClientData()方法;到此listenForever()的作用完成。handleClientData()方法代码如下。
/**
* When data is available on the socket, this method is called to run the
* command or throw an error if it can't.
*
* @throws SocketServerException
*/
private void handleClientData() throws SocketServerException {
try {
input.setLength(0); // clear String res;
int a;
// (char) -1 is not equal to -1.
// ready is checked to ensure the read call doesn't block.
while ((a = in.read()) != -1 && in.ready()) {
input.append((char) a);
}
final String inputString = input.toString();
Logger.debug("Got data from client: " + inputString);
try {
final AndroidCommand cmd = getCommand(inputString);
Logger.debug("Got command of type " + cmd.commandType().toString());
res = runCommand(cmd);
Logger.debug("Returning result: " + res);
} catch (final CommandTypeException e) {
res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage())
.toString();
} catch (final JSONException e) {
res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR,
"Error running and parsing command").toString();
}
out.write(res);
out.flush();
} catch (final IOException e) {
throw new SocketServerException("Error processing data to/from socket ("
+ e.toString() + ")");
}
}
该方法接收客户端数据,用getCommand()方法获得AndroidCommand对象,然后执行runCommand()方法,获取结果。
/**
   * When {@link #handleClientData()} has valid data, this method delegates the
* command.
*
* @param cmd
* AndroidCommand
* @return Result
*/
private String runCommand(final AndroidCommand cmd) {
AndroidCommandResult res;
if (cmd.commandType() == AndroidCommandType.SHUTDOWN) {
keepListening = false;
res = new AndroidCommandResult(WDStatus.SUCCESS, "OK, shutting down");
} else if (cmd.commandType() == AndroidCommandType.ACTION) {
try {
res = executor.execute(cmd);
} catch (final Exception e) {
res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR, e.getMessage());
}
} else {
// this code should never be executed, here for future-proofing
res = new AndroidCommandResult(WDStatus.UNKNOWN_ERROR,
"Unknown command type, could not execute!");
}
return res.toString();
}
}

该方法判断命令数据属于哪种类型,命令有关机命令和动作命令,关注动作动作有很多。重点要看第一个else if中的AndroidCommandExecutor.execute()方法。AndroidCommandExecutor.java

/**
* Gets the handler out of the map, and executes the command.
*
* @param command
* The {@link AndroidCommand}
* @return {@link AndroidCommandResult}
*/
public AndroidCommandResult execute(final AndroidCommand command) {
try {
Logger.debug("Got command action: " + command.action()); if (map.containsKey(command.action())) {
return map.get(command.action()).execute(command);
} else {
return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND,
"Unknown command: " + command.action());
}
} catch (final JSONException e) {
Logger.error("Could not decode action/params of command");
return new AndroidCommandResult(WDStatus.JSON_DECODER_ERROR,
"Could not decode action/params of command, please check format!");
}
}
该命令执行后,开始执行你的所有操作内容了
if (map.containsKey(command.action())) {
return map.get(command.action()).execute(command);
} else {
return new AndroidCommandResult(WDStatus.UNKNOWN_COMMAND,
"Unknown command: " + command.action());
}
请注意map.get(command.action()).execute(command).在该类中找到map的代码:
static {
map.put("waitForIdle", new WaitForIdle());
map.put("clear", new Clear());
map.put("orientation", new Orientation());
map.put("swipe", new Swipe());
map.put("flick", new Flick());
map.put("drag", new Drag());
map.put("pinch", new Pinch());
map.put("click", new Click());
map.put("touchLongClick", new TouchLongClick());
map.put("touchDown", new TouchDown());
map.put("touchUp", new TouchUp());
map.put("touchMove", new TouchMove());
map.put("getText", new GetText());
map.put("setText", new SetText());
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("getSize", new GetSize());
map.put("wake", new Wake());
map.put("pressBack", new PressBack());
map.put("dumpWindowHierarchy", new DumpWindowHierarchy());
map.put("pressKeyCode", new PressKeyCode());
map.put("longPressKeyCode", new LongPressKeyCode());
map.put("takeScreenshot", new TakeScreenshot());
map.put("updateStrings", new UpdateStrings());
map.put("getDataDir", new GetDataDir());
map.put("performMultiPointerGesture", new MultiPointerGesture());
map.put("openNotification", new OpenNotification());
}
map是<String,CommandHandler>形式的map。value值对应的都是对象,这些对象继承自CommandHandler,都有execute方法,该方法就是根据命令的不同调用不同的对象来执行相关代码获取结果。从map的定义可以看出,appium手机的命令很丰富,我们可以在这里加入我们需要的操作代码、

我们来看看click方法,研究下(Click.java)。

package io.appium.android.bootstrap.handler;

import com.android.uiautomator.core.UiDevice;
import com.android.uiautomator.core.UiObjectNotFoundException;
import io.appium.android.bootstrap.*;
import org.json.JSONException; import java.util.ArrayList;
import java.util.Hashtable; /**
* This handler is used to click elements in the Android UI.
*
* Based on the element Id, click that element.
*
*/
public class Click 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()) {
try {
final AndroidElement el = command.getElement();
el.click();
return getSuccessResult(true);
} catch (final UiObjectNotFoundException e) {
return new AndroidCommandResult(WDStatus.NO_SUCH_ELEMENT,
e.getMessage());
} catch (final Exception e) { // handle NullPointerException
return getErrorResult("Unknown error");
}
} else {
final Hashtable<String, Object> params = command.params();
final Double[] coords = { Double.parseDouble(params.get("x").toString()),
Double.parseDouble(params.get("y").toString()) };
final ArrayList<Integer> posVals = absPosFromCoords(coords);
final boolean res = UiDevice.getInstance().click(posVals.get(0),
posVals.get(1));
return getSuccessResult(res);
}
}
}

该类就一个execute方法,execute方法先判断传入的参数对象是坐标值还是元素值,是元素值直接调用AndroidElement中的click方法,;是坐标的话,会调用UiDevice的click方法,它是uiautomator包中的类。appium在android api16以上使用uiautomator机制。再分析一个touchDown命令,如果传过来的命令后缀是touchDown,那么它会调用TouchDown对象的execute方法。

map.put("touchDown", new TouchDown());

TouchDown.java

package io.appium.android.bootstrap.handler;

import com.android.uiautomator.common.ReflectionUtils;
import com.android.uiautomator.core.UiObjectNotFoundException;
import io.appium.android.bootstrap.Logger; import java.lang.reflect.Method; /**
* This handler is used to perform a touchDown event on an element in the
* Android UI.
*
*/
public class TouchDown extends TouchEvent { @Override
protected boolean executeTouchEvent() throws UiObjectNotFoundException {
printEventDebugLine("TouchDown");
try {
final ReflectionUtils utils = new ReflectionUtils();
final Method touchDown = utils.getControllerMethod("touchDown", int.class,
int.class);
return (Boolean) touchDown.invoke(utils.getController(), clickX, clickY);
} catch (final Exception e) {
Logger.debug("Problem invoking touchDown: " + e);
return false;
}
}
}
该方法用到反射机制,调用uiautomator隐藏api执行操作

手机自动化测试:appium源码分析之bootstrap一的更多相关文章

  1. 手机自动化测试:appium源码分析之bootstrap三

    手机自动化测试:appium源码分析之bootstrap三   研究bootstrap源码,我们可以通过代码的结构,可以看出来appium的扩展思路和实现方式,从中可以添加我们自己要的功能,针对app ...

  2. 手机自动化测试:appium源码分析之bootstrap二

    手机自动化测试:appium源码分析之bootstrap二   在bootstrap项目中的io.appium.android.bootstrap.handler包中的类都是对应的指令类, priva ...

  3. 手机自动化测试:appium源码分析之bootstrap十七

    手机自动化测试:appium源码分析之bootstrap十七   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣 ...

  4. 手机自动化测试:appium源码分析之bootstrap十六

    手机自动化测试:appium源码分析之bootstrap十六   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣 ...

  5. 手机自动化测试:appium源码分析之bootstrap十五

    手机自动化测试:appium源码分析之bootstrap十五   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣 ...

  6. 手机自动化测试:appium源码分析之bootstrap十四

    手机自动化测试:appium源码分析之bootstrap十四   poptest(www.poptest.cn)是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开 ...

  7. 手机自动化测试:appium源码分析之bootstrap十三

    手机自动化测试:appium源码分析之bootstrap十三   poptest(www.poptest.cn)是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开 ...

  8. 手机自动化测试:appium源码分析之bootstrap十一

    手机自动化测试:appium源码分析之bootstrap十一   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣 ...

  9. 手机自动化测试:appium源码分析之bootstrap十二

    手机自动化测试:appium源码分析之bootstrap十二   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣 ...

随机推荐

  1. 征服恐惧!用 Vim 写 iOS App

    我们都知道 Vim 和 Emacs 都是文本编辑器中的上古神器,你也许用 ctags,cscopes 配合 Vim 完成过大型 C 或者 C++ 的开发,你也许配合过其他插件,完成过 JavaScri ...

  2. 纪中集训 Day 5

    不知不觉已经day 5了啊 今天早上醒来,觉得要AK的节奏,结果就立flag了 - - 30分QAQ 其实第一题应该得想得到的,还有T2也能够解决的(话说后来看别人的代码写的好赞啊QAQ) 然后下午就 ...

  3. 微信公众平台开发-微信服务器IP接口实例(含源码)

    微信公众平台开发-access_token获取及应用(含源码)作者: 孟祥磊-<微信公众平台开发实例教程> 学习了access_token的获取及应用后,正式的使用access_token ...

  4. Java代码审查工具findbugs的使用总结

    findbugs简介 Findbugs是一个Java代码静态分析工具,可以用它来检查源代码中可能出现的问题,以期尽可能在项目的初始阶段将代码问题解决. FindBugs检查的是类或者JAR文件即字节代 ...

  5. 可扩展标记语言XML

    XML简述 XML用于描述数据,是当前处理结构化文档信息的有力工具.与操作系统编程语言的开发平台无关,可以实现不同系统之间的数据交互. 结构 <?xml version="1.0&qu ...

  6. 用TTL线在CFE环境下拯救半砖wrt54g路由器

    缘起:路由器被刷成半砖 Linksys wrt54gs v4路由器,已刷入 tomato-dualwlan 1.23.使用数年,未出现任何故障. 在日用的wifi网络上,通过web界面刷入了错误的to ...

  7. 微信企业号接收消息(使用SpringMVC)

    微信企业号接收消息(使用SpringMVC) 微信企业号接收消息(使用SpringMVC) 将应用设置在回调模式时,企业可以通过回调URL接收员工回复的消息,以及员工关注.点击菜单.上报地理位置等事件 ...

  8. css兼容问题 ie6,7

    H5标签兼容 元素浮动之后能设置宽度的话就给元素加宽度,如果需要元素宽度是内容撑开,就给他里面的块元素加上浮动 第一块加浮动,第二块加margin等于第一块元素在IE6下会有间隙问题 IE6下子元素超 ...

  9. 学习一点Markdown的基本知识

    本文于2017年3月18日首发于LinkedIn,请参考链接 这个世界的进步是由一些"懒"的人推动的.今天讲的这个Markdown,其实也是因为一批厌倦了HTML的各种标签的语法, ...

  10. DAX基础入门 - 30分钟从SQL到DAX -- PowerBI 利器

    看到漂漂亮亮的PowerBI报表,手痒痒怎么办?! 有没有面对着稀奇古怪的DAX而感到有点丈八金刚摸不着头脑或者干瞪眼?! 有没有想得到某个值想不出来DAX怎么写而直跳脚!? 看完这篇文章,你会恍然大 ...