Macaca Macaca 基础原理浅析

Yinxl · 2016年12月07日 · 最后由 tangoliver 回复于 2023年02月14日 · 3825 次阅读
本帖已被设为精华帖!

导语

前面几篇文章介绍了在 Macaca 实践中的一些实用技巧与解决方案,今天简单分析一下 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

为了方便大家查看,宝宝费了好大劲画了下面这张图 (觉得不错的给个赞吧):

structure

备注:上图所有模块均可以在官方 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 的组成模块以及他们各自的作用,下面我们看一下各个模块是如何组装起来实现自动化测试流程的,宝宝同样费了很大劲画了一张图如下:

flow

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

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

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

1. 启动 macaca server

➜  bootstrap git:(master) ✗ 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

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

2. 执行用例

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

image

首先我们来看一下对应用例启动的 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);

}

在这段代码中,我们做的工作是根据不同的平台设置用例的一些基础启动信息,包含平台类型、安装包地址、设备 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: /session:POST to http://30.30.180.23:8900/session:POST 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\"}"}

经过如上步骤后,连接便已经成功建立了,下一步再分析一下一个具体操作,以登录为例,对应的 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);

        }
    }

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

>> responseHandler.js:11:12 [master] pid:5503 Recieve HTTP Request from Client: method: POST url: /wd/hub/session/abe8f19c-76ea-4bb0-b5b9-d69e3ce9b798/element, jsonBody: {"using":"name","value":"Login"}
>> proxy.js:54:14 [master] pid:5503 Proxy: /wd/hub/session/abe8f19c-76ea-4bb0-b5b9-d69e3ce9b798/element:POST to http://30.30.180.23:8900/session/6A1D2ED3-37BD-449C-A128-2E72DEF4CBF9/element:POST with body: {"using":"name","value":"Login"}
>> proxy.js:67:16 [master] pid:5503 Got response with status 200: {"value":{"using":"name","value":"Login","description":"unable to find an element"},"sessionId":"abe8f19c-76ea-4bb0-b5b9-d69e3ce9b798","status":7}
>> session.js:107:14 [master] pid:5503 Send HTTP Respone to Client: {"value":"{\"using\":\"name\",\"value\":\"Login\",\"description\":\"unable to find an element\"}","sessionId":"abe8f19c-76ea-4bb0-b5b9-d69e3ce9b798","status":7}

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

一点题外话

关于 Macaca 使用中的问题,很多同学会直接去社区里提问,但是很多时候大家问的问题都是类似的,这种情况建议大家先去查看一下官方仓库的 issues,看看有没有人遇到自己同样问题的,如果没有,可以新建 issue。查看 issue 之前先看下自己问题所属的模块,比如如果问题在 wd.java 的使用中,可以去 wd.java 仓库下查看 issue:https://github.com/macacajs/wd.java/issues

小结

如上简单总结了 Macaca 的基础原理,提供一个小窍门是大家可以对照控制台输出与文章中的流程图一一对应,这样就能大体了解整个流程的数据流向,从而就能参透 Macaca 的基础原理了。个人水平有限,如有不当,欢迎指正。
后面会陆续放出自己在实践中的其他心得与经验,敬请期待,也欢迎大家交流讨论。

附录

“Macaca 开源社区” 群的钉钉群号: 11775486(欢迎入群讨论,钉钉顶部搜索框搜索群号加入即可)

更多相关文章

UI 自动化框架调研总结
从无到有搭建 Macaca 环境 (forMac)
Macaca-Java 版入门指南
UI 自动化 Macaca-Java 版实践心得
UI 自动化利器 - 为你的应用自动添加控件 ID 探索
Macaca 基础原理浅析

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 36 条回复 时间 点赞

11775486 现在这个钉钉群找不到了,请问还有其他的群吗?

子非鱼 行业流行测试框架对比 中提及了此贴 03月18日 21:59

请教下,服务器与 Android 真机之间是怎么连接的?

macaca java 如何切换 windowHandles,我通过 webDriver.contexts.getWindowHandle() 这种方式,获取不到 handle

求助,pc 桌面版基于 electronjs 的桌面版应用的 UI 页面中,需要使用到鼠标右键的操作,试问鼠标右键的模拟操作 macaca 如何实现呢,api 中未发现,借问求分享哦

Yinxl Macaca 基础原理浅析 中提及了此贴 10月29日 14:05
达峰的夏天 macaca 的一些问题请教 中提及了此贴 08月12日 16:04
Yinxl 从无到有搭建 Macaca 环境 (forMac) 中提及了此贴 06月28日 20:10
Yinxl UI 自动化框架调研总结 中提及了此贴 06月28日 20:09
Yinxl UI 自动化 Macaca-Java 版实践心得 中提及了此贴 06月28日 20:08
Yinxl Macaca-Java 版入门指南 中提及了此贴 06月28日 20:08

#34 楼 @junhe 谢谢👍 ,大神的用例组织太经典,得好好学学。

请问用例应该怎么组织呢,目前提供的一些 demo 都很简单,用在项目中的话,不知道怎么组织用例

终于有架构图了 赞 期待再解释下跟 appium 的差别 以及为什么速度快

Yinxl #32 · 2016年12月19日 Author

一个请求对应一个动作还是一系列动作要看不同请求不同分析的,如果终端响应并输出结果了但是真机上没有执行对应的动作首先要看当前连接的是不是真机这台机器 (有没有可能启动了模拟器但是你没看到),另外可以透过终端的输出看一下当前的动作是什么,再对应着看客户端的反映

请问楼主,在 Macaca 内部执行过程当中,是否是一个 请求对应一系列的执行动作,还是一个请求只对应一个执行动作?
当我 自启动一个 Macaca Server 的时候,然后模拟着像这个 Server 发送满足条件的请求,但是永远都只有终端的响应输出结果,而且请求都是 OK 的,但是真机设备上没有任何的执行动作,请问这是为什么了?

IOS 真机上感觉运行速度很慢,不知道为什么

牛逼

Yinxl #28 · 2016年12月14日 Author

#23 楼 @melody902 localhost:3456 这个是默认的 不用自己指定的 你说的看不到内容是指 app-inspector 看不到内容吗? 最好附带一下截图或者日志哈 没太明白你的意思 嘿嘿

Yinxl #26 · 2016年12月14日 Author

#22 楼 @happystone 我有一篇文章就是讲的这个问题哦:https://testerhome.com/topics/6602

macaca 越来越火了哈,macaca 与 appium 相比有哪些明显的优势呢?有点重复造轮子的感觉

#17 楼 @apple863 灰常感谢····也谢谢君禾大美女 感恩

#17 楼 @apple863 没事了·······直接点链接的时候把后面的 emoji 图片也带进去了,删了就好了 +_+

#17 楼 @apple863 /(ㄒ o ㄒ)/~~ ·········心好累···你给的链接打开是 404 的提示

Yinxl #21 · 2016年12月12日 Author

#17 楼 @apple863 👍 很详细的回复 赞!

#6 楼 @876374178 兄弟,给你这个链接,我和你的疑问一样,我总结的经验是多看日志,注意关键字,熟练使用百度/谷歌。这是我找到的结果,macaca 用 js 编写用例会用到的 mocha 框架,里面解释了 descibe 和 it 的意思等等。http://www.ruanyifeng.com/blog/2015/12/a-mocha-tutorial-of-examples.html☺,希望对你有用

Yinxl #30 · 2016年12月10日 Author

#15 楼 @AllocAndInit 这个是封装在 Macaca 框架里面的,对于写自动化用例来说是透明的哈,我们基本用不到的

请问 iOS 的 remote-debug 这个东西怎么使用?楼主能解释一下吗?

支持阿里的这种分享精神,非常棒!💪 👍 👍 👍

思路非常清晰,图也画得漂亮👍 👍 👍

Yinxl #10 · 2016年12月08日 Author

#11 楼 @Tank007 哈哈 用 Mac 自带的 keynote 哈哈

匿名 #16 · 2016年12月08日

图用什么工具画的,眼前一亮

#8 楼 @junhe 是的需要用 js 写用例·······

Yinxl #15 · 2016年12月08日 Author

#7 楼 @testly 拿走不谢 哈哈哈哈

Yinxl #14 · 2016年12月08日 Author

#6 楼 @876374178 你是要用 Js 写用例吗?还是 Java? 关于 Java 的可以参考一下我前面文章中给出的一个 demo,有一些注释,看起来应该能比较清楚的。 inspector 的使用我随后发一篇文章出来吧

感谢分享,图我可以借鉴一下么?

想问一下有没有关于脚本是如何写的这样的教程,用 node.js 的,一个脚本是如何完成的,已 macaca-mobile-sample.test.js 这个脚本为例,还有关于 inspector 的使用,另外一篇关于 inspector 的使用的文档太简单了,inspector 启动后具体该如何查找控件,这些内容有什么比较详细的文档,或者网课可以看,看到现在完全没有一个思路,一个测试脚本是如何产生的,只知道环境搭一下,然后去看 node.js 的语言,然后再去看脚本,但是脚本里面的内容又看不懂,
比如说这个,我知道这个是测试登录是否成功的内容,但是为什么这样写,中间的内容如果更换了其他的 APP 需要做些什么变动修改,想知道这些东西,问题貌似有点多········

非常细致,清晰明了

好文,谢谢分享

#2 楼 @Lihuazhang 哈哈 值了 谢谢!

图画的很漂亮

恒温 将本帖设为了精华贴 12月07日 19:35
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册