收藏官网首页
查看: 20315|回复: 0

[杂谈] Macaca 基础原理浅析

21

主题

24

帖子

209

积分

中级会员

Rank: 3Rank: 3

积分
209
跳转到指定楼层
楼主
发表于 2017-3-30 11:21:44 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
校园创客福利
今天简单分析一下Macaca的基础原理。这篇文章将以前面所分享的UI自动化Macaca-Java版实践心得中的demo为基础,进行一下实例讲解。
  Macaca的基本组成  通过对源码各个模块的分析,可以帮助我们对Macaca的整体构成有一个基础的认识。Macaca已经开源,相关的源码在对应的github上都可以下载:

  https://github.com/macacajs

  大家会在alibaba集团的开源github上找到macaca的另一个仓库https://github.com/alibaba/macaca/,关于这两个仓库的关系这里简单讲一下,由于主仓库https://github.com/macacajs模块众多,所以在alibaba集团下的github上采用了alibaba/macaca,以方便管理,大家如果需要源码的话需要去macaca的主仓库查看,也就是https://github.com/macacajs




  备注:上图所有模块均可以在官方github上找到对应的源码 https://github.com/macacajs

  模块拆分讲解:

  Macaca

  1. macaca-cli

  Macaca提供的命令行工具

  $macaca server 启动server

  $macaca server --verbose 启动server并打印详细日志

  $macaca doctor 检验当前macaca环境配置

  2. app-inspector

  macaca提供的元素查找工具,可以将app视图的结构以布局结构树的格式在浏览器上展示出来,用过点击某个元素,就可以方便的查询到该控件的基本信息,以方便查找。具体使用可参考官网: https://macacajs.com/inspector

  3. UI Recorder

  macaca提供的脚本录制工具,可以通过录制获得脚本,对于入门同学很有帮助。https://macacajs.com/recorder

  WebDriver-Server

  Macaca是按照经典的Server-Client设计模式进行设计的,也就是我们常说的C/S架构。WebDriver-server部分便充当了server这部分的角色,他的职责就是等待client发送请求并做出响应。

  WebDriver-Client

  client端简单来讲就是我们的测试代码,我们测试代码中的一些行为,比如控件查找、点击等,这些行为以http请求的方式发送给server,server接收请求,并执行相应操作,并在response中返回执行状态、返回值等信息。

  也正是基于这种经典的C/S架构,所以client端具有跨语言的特点,macaca-wd,wd.java,wd.py分别是Macaca团队针对Js Java 以及Python的封装,只要能保证client端按照指定的要求发送Http请求,任意语言都可以。

  DriverList

  自动化要在不同的平台上跑,需要有对应平台的驱动,这部分驱动接收到来自server的操作命令,驱动各自平台的底层完成对应的操作。

  1. Android

  Macaca针对安卓平台的驱动集合

  macaca-android 安卓驱动

  macaca-adb 封装了安卓的adb命令,来实现一些adb的操作,比如安装、卸载、启动app、获取设备列表这些操作

  android-unicode 经过封装后的输入法,解决中文输入的问题

  uiautomator-client 将来自server的操作指令转换为UIAutomator可以识别的指令,驱动uiautomator完成对应的操作

  android-performance 用于自动化测试安卓性能相关的支持

  2. iOS

  Macaca针对iOS平台的驱动集合

  macaca-ios iOS驱动

  xctest-client 同安卓的uiautomator-client异曲同工,对XCUITest的封装,将来自server的操作指令转换为XCUITest可以识别的指令,驱动XCUITest完成对应的操作

  ios-simulator 用于对ios模拟器的支持,可以通过模拟器运行用例

  remote-debug 用于远程调试

  3. Hybrid

  Macaca针对Hybrid的驱动集合。

  macaca-chrome web测试驱动

  macaca-chromedriver 驱动chrome浏览器

  ios-webkit-debug-proxy 适用于iOS平台对webview的调试

  4. Electron

  Macaca针对pc端网页应用的支持

  macaca-electron

  Macaca执行流程图

  了解了Macaca的组成模块以及他们各自的作用,下面我们看一下各个模块是如何组装起来实现自动化测试流程的,宝宝同样费了很大劲画了一张图如下:



  结合实例讲解Macaca基本原理:

  以文章开始提到的demo为例(client以Java版为例) demo地址

  源码克隆到本地并配置好Macaca相关环境后,我们来执行一次用例:

  1. 启动macaca server

  •   ➜ bootstrap gitmaster) ✗ macaca server --verbose
  •   >> request.js:24:12 [master] pid:5499 get remote update info failed.
  •   >> index.js:17:12 [master] pid:5503 webdriver server start with config:
  •   { port: 3456,
  •   verbose: true,
  •   always: true,
  •   ip: '30.30.180.23',
  •   host: 'MacBook-Pro.local',
  •   loaded_time: '2016-12-07 17:00:22' }
  •   >> middlewares.js:17:10 [master] pid:5503 base middlewares attached
  •   >> router.js:129:10 [master] pid:5503 router set
  •   >> webdriver sdk launched

[color=rgb(51, 102, 153) !important]复制代码



  从这一步打印的信息我们可以看到,这一步实际上执行的是流程图中第一步的操作,启动server,建立连接,然后server返回所连接的ip以及端口号,因为我们是本地跑,所以ip实际上是本机的ip地址

  2. 执行用例

  以SampleTest为例,右键执行junitTest,稍作等待,就会看到系统自动启动了ios的模拟器并跑起来了用例。执行过程中的某个截图如下:



  首先我们来看一下对应用例启动的client端核心代码:

  •   @Before
  •   public void setUp() throws Exception {
  •   // 清除日志记录
  •   ResultGenerator.clearOldData();
  •   //清理截图重新记录
  •   File file = new File(Config.ScreenshotPath);
  •   deleteOldScreen(file);
  •   // 初始化应用基础信息
  •   JSONObject props = new JSONObject();
  •   if (Config.PLATFORM.equals("ios")) {
  •   // 创建ios实例
  •   props.put("app", Config.IOS_APP);
  •   props.put("platformName", Config.IOS_PLATFORM_NAME);
  •   props.put("deviceName", Config.IOS_DEVICE_NAME);
  •   driver.setCurPlatform(PlatformType.IOS);
  •   } else {
  •   //创建安卓实例
  •   props.put("app", Config.ADR_APP);
  •   props.put("platformName", Config.ADR_PLATFORM_NAME);
  •   driver.setCurPlatform(PlatformType.ANDROID);
  •   }
  •   // 覆盖安装
  •   props.put("reuse", Config.REUSE);
  •   JSONObject desiredCapabilities = new JSONObject();
  •   desiredCapabilities.put("desiredCapabilities", props);
  •   driver.initDriver(desiredCapabilities);
  •   }

[color=rgb(51, 102, 153) !important]复制代码



  在这段代码中,我们做的工作是根据不同的平台设置用例的一些基础启动信息,包含平台类型、安装包地址、设备id、是否覆盖安装等参数,设置完成后,通过driver.initDriver(desiredCapabilities)这个操作启动driver,这个过程便会按照流程图中的第二个步骤发送http请求,server会接收到这个请求并创建一个session,在这次的用例执行中,所有的操作都会基于这个session进行,来看一下针对这个操作控制台所打印的信息(为方便突出主要过程省略了部分无关日志):

  •   >> responseHandler.js:11:12 [master] pid:5503 Recieve HTTP Request from Client: method: POST url: /wd/hub/session, jsonBody: {"desiredCapabilities":{"app":"/Users/Macaca/github/bootstrap/app/ios-app-bootstrap.zip","reuse":"3","platformName":"iOS","deviceName":"iPhone 6"}}
  •   >> session.js:47:10 [master] pid:5503 Creating session, sessionId: abe8f19c-76ea-4bb0-b5b9-d69e3ce9b798.
  •   >> helper.js:196:12 [master] pid:5503 Unzipping local app form /Users/Macaca/github/bootstrap/app/ios-app-bootstrap.zip
  •   >> macaca-ios.js:194:10 [master] pid:5503 Get available devices(...省略设备列表)
  •   ...省略部分信息
  •   >> proxy.js:54:14 [master] pid:5503 Proxy: /sessionOST to http://30.30.180.23:8900/session\"\"OST with body: {"desiredCapabilities":{"bundleId":"xudafeng.ios-app-bootstrap","app":"/var/folders/lf/lmrfrj9s4xn76wq_4k3x92380000gn/T/ios-app-bootstrap.app/","platformName":"iOS"}}
  •   >> proxy.js:67:16 [master] pid:5503 Got response with status 200: {"value":{"sessionId":"6A1D2ED3-37BD-449C-A128-2E72DEF4CBF9","capabilities":{"device":"iphone","browserName":"ios-app-bootstrap","sdkVersion":"10.1","CFBundleIdentifier":"xudafeng.ios-app-bootstrap...
  •   >> responseHandler.js:47:14 [master] pid:5503 Send HTTP Respone to Client: {"sessionId":"abe8f19c-76ea-4bb0-b5b9-d69e3ce9b798","status":0,"value":"{\"app\":\"/var/folders/lf/lmrfrj9s4xn76wq_4k3x92380000gn/T/ios-app-bootstrap.app/\",\"reuse\":\"3\",\"platformName\":\"iOS\",\"deviceName\":\"iPhone 6\"}"}

[color=rgb(51, 102, 153) !important]复制代码



  经过如上步骤后,连接便已经成功建立了,下一步再分析一下一个具体操作,以登录为例,对应的client端的代码如下:

  (因为框架层封装了一些操作,所以代码看上去比较少,具体的控件查找部分看不到,有需要详细了解的可以研究源码)

  •   // SampleTest.java
  •   @Test
  •   public void test () throws Exception {
  •   // 处理登录
  •   LoginPage loginPage = new LoginPage("登录页");
  •   loginPage.setDriver(driver);
  •   if (loginPage.hasPageShown(LoginPageUI.LOGIN_BTN)) {
  •   saveScreen(loginPage.pageDesc);
  •   ResultGenerator.loadPageSucc(loginPage);
  •   loginPage.login("test", "123");
  •   } else {
  •   ResultGenerator.loadPageFail(loginPage);
  •   }
  •   }

[color=rgb(51, 102, 153) !important]复制代码



  对应登录按钮的查询操作,我们会在控制台上看到如下的日志:


[color=rgb(51, 102, 153) !important]复制代码



  在上面的日志中我们可以看到,当我们查找登录按钮的时候,client发送了一个http请求给server,请求的操作是element(这个表示控件查找),参数是{"using":"name","value":"Login"},这是告诉server我们要找的这个控件的name属性是Login,server收到这个请求,通过router路由转发给iOS的驱动(在启动driver的时候已经设置的平台类型,因此这里能知道找ios),iOS驱动收到请求驱动XCUITest框架对模拟器上的目标app执行对应的控件查找操作,得到response后原路返回给client,这样就完成了一次请求的完整的生命周期。



您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

加入Q群 返回顶部

版权与免责声明 © 2006-2024 Gizwits IoT Technology Co., Ltd. ( 粤ICP备11090211号 )

快速回复 返回顶部 返回列表