appium框架之bootstrap

(闲来无事,做做测试..)最近弄了弄appium,感觉挺有意思,就深入研究了下。

看小弟这篇文章之前,先了解一下appium的架构,对你理解有好处,推荐下面这篇文章:testerhome

appium是开源项目,可以获得源码:appium-master

在eclipse中用maven导入会发现有2个项目:bootstrap和sauce_appium_junit。

sauce_appium_junit是一些测试用例的集合,帮助学习的。bootstrap就是appium架构中放在手机端的一个服务器。就从它开始吧。

bootstrap结构

如图所示为bootstrap的项目结构

bootstrap作用

bootstrap在appium中是以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);
    }

  }
}

该类继承自UiAutomatorTestCase。所以它才能通过adb shell uiautomator runtest AppiumBootstrap.jar -c io.appium.android.bootstrap.Bootstrap被执行。

该类很简单,就是启动线程,监听4724端口,该端口与appium通信。

然后走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()方法,获取直接的结果。那么该方法的作用就转移到了runCommand()。所以现在就来看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里存放的对象是哪些,那么在该类中找到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可以操作手机的命令还不少,我用过的有scrollTo,updateStrings,getDataDir等,上面还有截图、打开通知栏、按下等还没用过,但通过这些命令你也可以了解appium可以做哪些事。

继承CommandHandler的对象有很多,我挑一个来讲讲它具体是干嘛的,其他的我以后会挨个讲,就挑click吧。

加入现在传过来的命令后缀是click的话,那么它会调用Click对象的execute方法。

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的人都知道它是uiautomator包中的类。所以说appium在api16以上的机器上使用的uiautomator机制。貌似有人觉得这好像easy了点。那好吧,我们再分析一个touchDown命令,如果传过来的命令后缀是touchDown,那么它会调用TouchDown对象的execute方法。

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

这个类里面的execute方法就有点意思啦。

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

时间: 2024-10-19 19:14:49

appium框架之bootstrap的相关文章

H5框架之Bootstrap(二)

H5框架之Bootstrap(二) 突然感觉不知道写啥子,脑子里面没水了,可能是因为今晚要出去浪,哈哈~~~提前提醒大家平安夜要回家哦,圣诞节生00000000000这么多蛋....继续 上一篇的已经把bootstrap了解个大概了,接下来我们就开始学习一下它里面的东西. 浏览器支持 旧的浏览器可能无法很好的支持 Bootstrap 支持 Internet Explorer 8 及更高版本的 IE 浏览器 CSS源码研究 我们不是在head里面引入了下面这些文件么 <!-- 新 Bootstra

H5框架之Bootstrap(一)

H5框架之Bootstrap(一) 接下来的时间里,我将和大家一起对当前非常流行的前端框架Bootstrap进行速度的学习,以案例的形式.对刚开始想学习Bootstrap的同学而找不着边的就很有帮助了.如果你想详细的学习Bootstrap,建议去http://noob.d8jd.com/noob/5.html这里学习,说多了,开始走两步.... 第一步:下载Bootstrap 看这个大B就知道有多NB了,哈哈~~~~ 英文也说它最流行的HTML,CSS和JS框架,至于它有哪些好处,谁用谁知道.

RF+Appium框架自动化测试系列一之(Mac下Appium环境搭建)万事开头难

Mac下Appium环境搭建: 消失了3个月,有一段时间没来园子更新博客了,各位看官见谅哈哈,消失是因为TestYao刚换了工作环境没外网,好多笔记没能及时的记录分享,以后有时间慢慢补上吧,这段时间主要接触了移动端app的自动化测试,公司为了快速把移动端自动化这块搞起来,试用几款目前流行的工具,腾讯的xtest定制版.神州数码的自动化测试平台.开源rf+appium框架. 经过一段试用最终还是选择了rf+appium主要原因有: 1.开源,网上社区活跃 2.扩展性强,针对不同项目不同的需求,封装

旺财速啃H5框架之Bootstrap(四)

上一篇<<旺财速啃H5框架之Bootstrap(三)>>已经把导航做了,接下来搭建内容框架.... 对于不规整的网页,要做成自适应就有点玩大了.... 例如下面这种版式的页面.... 对于这样的网站,要做成自适应,那你得多花点精力了,当然我不讲这个... 所以这里肯定是对那种"列很规律"的网站,例如cnblogs首页的版式,分左,中,右 由于中国传统艺术版本,或习惯什么的,很多东西都讲对称性,所以几乎所有网站都是很规律的,说多了,直接上(这里我只写主要CSS)

旺财速啃H5框架之Bootstrap(三)

好多天没有写了,继续走起 在上一篇<<旺财速啃H5框架之Bootstrap(二)>>中已经把CSS引入到页面中,接下来开始写页面. 首先有些问题要先处理了,问什么你要学bootstrap? 流行?写网页快?还是其它什么的?说实话,如果写页面很熟练的人(牛人),根本不会用任何框架写,为什么呢,它们太臃肿了,谁写一个页面能把它所有的CSS都应该用上,所有的类都定义上呢?所以很多代码对于页面来说都是多余的,还好上线后,引入的都是资源库,如果你有代码精简强迫症,那估计你要重写(覆盖)一些东

前端(三大框架、Bootstrap,jQuery,自我整理)

前端,HTML(超文本标记语言),CSS(层叠样式表)和JavaScript(脚本语言) HTML,通常说的h5,其实按标准来说,HTML4的后续版本不带编号了,并保证向前的兼容性 CSS的版本3,增加了translate(),能完成以前一定需要js才能做到的动画,同时增加了flex弹性盒子(响应式设计,提供一种更加有效的方式来对一个容器中的子元素进行排列.对齐和分配空白空间,以往使用float) 前端框架 一.Bootstrap 在Bootstrap的官网介绍中,Bootstrap is th

Java单体应用 - 常用框架 - 01.Bootstrap

原文地址:http://www.work100.net/training/monolithic-frameworks-bootstrap.html 更多教程:光束云 - 免费课程 Bootstrap 序号 文内章节 视频 1 概述 2 阅读对象 3 前置知识 4 为什么使用Bootstrap 5 Bootstrap包的内容 6 参考资源 请参照如上章节导航进行阅读 1.概述 Bootstrap 来自 Twitter,是目前很受欢迎的前端框架. Bootstrap 是基于 HTML.CSS.Jav

使用appium框架测试安卓app时,获取toast弹框文字时,前一步千万不要加time.sleep等等待时间。

使用appium框架测试安卓app时,如果需要获取toast弹框的文案内容,那么再点击弹框按钮之前,一定记得千万不要加time.sleep()等待时间,否则有延迟,一直获取不到: 获取弹框的代码: message=self.driver.find_element_by_xpath("//*[contains(@text,'成功添加到购物车')]")   原文地址:https://www.cnblogs.com/zhouchuanlun/p/12687890.html

跨平台前端框架学习——bootstrap,jquery,angular

按照我的理解前端开发的框架可以分为三种类型: UI框架:这部分如bootstrap,定义了一套css样式风格,帮助布局,提供了若干可以直接使用的组件,我们可以只使用它提供的组件而不用自己设计 UI 工具类框架:如jquery(pc端)/jquery mobile(移动端),虽然准确来说它们更是类库,但它们集成了有关 DOM操作,ajax交互,事件和动画的工具,能够帮助解决浏览器兼容,这里姑且当做框架 组织代码的框架:如angular,它不提供jquery一样(类似插件)的功能,它帮我们更好地组织