Macaca Macaca 自动遍历器 NoSmoke 发布公测

Samuel.ZhaoY · 2017年09月12日 · 最后由 孙志强 回复于 2022年02月18日 · 10147 次阅读
本帖已被设为精华帖!

目前在 macaca 提供的基础能力上研发出了一套多端深度遍历爬虫工具. 希望可以最大化减少 UI 测试脚本的编写
涵盖以下功能点:

  1. 支持iOS, Android,PC-Web 三个平台的自动化测试
  2. 同时可以通过以下三种层级方式, 根据使用者自身场景满足不同程度的定制化需求:
    2.1 YML 配置文件: 普通内容定制 轻度定制
    2.2 钩子: 个别流程,拦截定制 中度定制
    2.3 平台.js 文件覆盖: 按需选择流程接口, 覆盖定制 重度定制
  3. Mokey 的测试过程与macaca-reporter 联通,测试数据由 macaca-report 报表分析器统一沉淀并进行可视化展示.
    (目前 PC-Web , 以及 Reporter 对爬虫的支持还在调试当中)

项目源码地址: https://github.com/macacajs/NoSmoke
macaca-reporter 源码地址: https://github.com/macacajs/macaca-reporter

目前项目正在收尾阶段, 所以先发一个原型出来,希望收集一下大家的意见,发布版本会根据大家的意见进行调优.

1.运行效果

1.1 iOS

1.2 Android

1.3 PC-Web

2. 启动指令:

项目依赖:
请确保你安装了 macaca 系列的各个开源库:

npm i macaca-cli
npm i macaca-wd
npm i macaca-chrome
npm i macaca-android
npm i macaca-ios
git clone git@github.com:macacajs/NoSmoke.git
cd NoSmoke
npm install

2.1 配置文件

对于新手,建议直接使用 NoSmoke 项目中 public 路径下配置好的 yml 文件, 里面标注了 iOS/Android/Web-PC 三种爬取对象的简易配置, 可以直接跑起来

2.2 命令行

NoSmoke 根目录执行

macaca server --verbose

新开命令行窗口, 在 NoSmoke 根目录执行

npm run dev

3. 定制化说明

3.1 为了大家更好理解 NoSmoke 的实现原理,提供一份详细爬行流程图,.

screen shot 2017-09-06 at 6 00 16 pm

3.2 YML 配置文件选项详解:

# 1. Initialization option
desiredCapabilities:
  platformName: 'platform iOS/Android'
  deviceName: 'name of the device'
  app: 'url for downloading app here'

# 2. Crawling option
crawlingConfig:
  platform: 'iOS'
  targetElements:
    loginAccount:
      actionType  : 'action type: 1-click; 2-input'
      searchValue : 'the value to search'
      actionValue : 'the value to input'
  exclusivePattern: 'pushView/popView'
  clickTypes: 
    - 'array of clickable UI types: StaticText/Button'
  editTypes:
    - 'array of editable UI types: SecureTextField/TextFiled'
  horizontalScrollTypes:
    - 'array of horizontal scrollable UI types: PageIndicator'
  verticalScrollTypes:
    - 'array of vertical scrollable UI types: ScrollView'
  tabBarTypes:
    - 'array of control widget which behaves like a master in the 
    master-detail view structures: TabBar'
  exclusiveTypes:
    - 'array of disabled and esclusive UI types: NavigationBar'
  navigationBackKeyword:
    - 'array of words on which items should be regarded 
    as a back button: back'

3.3 钩子定制:

再精密设计的深度遍历算法对于不同的 UI 界面设计, 也不能完全保证能够 cover 大部分的遍历场景,因此通过钩子的形式给使用者提供不干预流程的前提下,定制遍历可能性
用户可以通过定制 /public/hooks.js 中的各个函数 对默认行为进行定制:

function Hooks(config, sessionId) {}

/**
 * Method to generate a unique digest which can identify a window, change the node.digest if you want.
 * @Params: $platform the name of the platform "iOS/android/web"
 * @Params: $source the raw json source of the current page
 * @Params: $node the node which needs to settle digest
 * @Returns: true to indicate the action has been handled and the default logic will not execute
 * to indicate the action has been handled
 * */
Hooks.prototype.checkDigest = function(platform, source, node, crawler) {
  return false;
};

/**
 * Method to sort a list of actions which will be later bind to a crawling node object, return the list of actions.
 * @Params: actions the array of actions which can be further sorted.
 * @Params: crawler the crawler instance which contains the context information as well as crawler config.
 * @Returns: actions the sorted actions which should be bind to the crawling node.
 * */
Hooks.prototype.sortActionPriority = function(actions, crawler) {
  return actions;
};

/**
 * Method to perform action for the current platform.
 * @Params: actions the actions which belongs to current active node, user can determine the priority of action execution
 * @Params: crawler the crawler instance which contains the context information as well as crawler config
 * @Returns: true to indicate the action has been handled and the default logic will not execute
 * */
Hooks.prototype.performAction = function(actions, crawler) {
  return false;
};

/**
 * Method to analysis and insert tab nodes for a master-detail view structure.
 * @Params: sourceArray the array of elements which belongs to the candidate tab node.
 * @Params: crawler the crawler instance which contains the context information as well as crawler config
 * @Returns: true to indicate the action has been handled and the default logic will not execute
 * */
Hooks.prototype.insertTabNode = function (sourceArray, crawler) {
  return false;
};

3.4 函数覆盖:

用户可以参考 crawler.js 延伸的 android.js, ios.js, web.js 对 crawler 中特定行为进行覆盖定制, 比如说 ios.js 中对个别接口针对 iOS 平台做了定制:

let NSCrawler = require('./crawler').NSCrawler;

// Parent must be an array of child elements
NSCrawler.prototype.insertXPath = function (parent, child) {
  let prefix = this.config.platform === 'iOS' ? 'XCUIElementType' : '';

  /** scan and check index path for once and only once*/
  let currentTypeCount = child.type + '_count';
  if (!parent[currentTypeCount]) {
    for (let i = 0; i < parent.children.length; i++) {
      currentTypeCount = parent.children[i].type + '_count';
      if (!parent[currentTypeCount]) {
        parent[currentTypeCount] = 1;
      } else {
        parent[currentTypeCount]++;
      }
      parent.children[i].pathInParent = parent[currentTypeCount];
    }
  }

  let currentIndex = child.pathInParent;
  child.xpath = (parent.xpath ? parent.xpath : '//' + prefix + 'Application[1]') + '/' + prefix + child.type + '[' + currentIndex + ']';
};

exports.NSCrawler = NSCrawler;
共收到 94 条回复 时间 点赞

请开始你的表演。(有的章节还是空的,图呢?)

老马 #1 回复

稍等

老马 #1 回复

好了, 过滤了一下无关内容

目前还在测试阶段, 欢迎大家试用提供反馈.

本质上还是 monkey 么?比 monkey 瞎点相比,更有目标的点?

恒温 将本帖设为了精华贴 09月12日 15:55

我理解的爬虫类测试应该是一种特殊规则的 monkey 吧,属于 monkey test 范畴

Baozhida #5 回复

这个是通过分析窗口树,生成节点, 通过深度遍历算法点击每一个按钮, 同一个按钮不会点击两遍, 一层一层往下层界面进行遍历. 并且支持多个平台, 对接了报告收集器. 跟一般的随机 mokey 不是一个层面的东西.

  • 钩子配置没文档啊
  • 持续集成实践文档也需要补充

钩子的接口注释就是说明, 要用某一个钩子的时候直接在那段接口里面写你的实现.

强烈关注!!

3.2 3.3 3.4 这些 最好能举例 举例具体的 然后 怎么根据示例写配置 或 改这些配置

老马 #89 回复

恩 会尽快完善

游戏的元素一个界面就是一坨 是不是和这个无缘了
😂

董文华 #16 回复

非 view 体系的,我觉得可以指定位置,或者定制一个 monkey 规则,看场景吧

董文华 #16 回复

可以试试, 因为深度遍历本身只是一套算法, 至于怎么筛选元素,怎么定位, 都应该是可以根据平台定制的.

董文华 #16 回复

遍历的一个关键点是 区分不同的视觉窗口,对于 UI 变动频率高的页面, 可以使用钩子里的 checkDigest 方法用你自己的实现区分页面

😃 很棒...想问下, 自动遍历速度快吗?还是和目前执行用例速度相当.

孟德功 #21 回复

因为涉及到 界面分析的操作,还有页面比对,会慢一些, 但不影响使用

我想问一下,在遍历中突然的弹窗界面怎么处理?另外按一下返回直接跳到两级的界面的这种遍历怎么处理?

23楼 已删除
0x88 #22 回复
  1. 目前会自动取消 alert 弹窗. 2. 调到两级的意思是什么? 窗口连续跳了两次吗?爬行的时候是检索最近这一次窗口的元素信息,识别窗口的,中间那次短时间的窗口会被忽略

我的意思是按一下物理返回会返回两个层级的界面,你会怎么处理?我这边也是关了弹窗,但就是有些弹窗里面的控件也是需要遍历,这个有点蛋疼。

0x88 #25 回复

设计的时候有考虑这个 case, 比如说对于 tabview 这样的 master - detail 视窗结构, 你可以在 config 中配置什么样的 element type 是一个 上层页面控件, 这样扫描的时候会把一个页面分成两层 node , controller 这层在上面一个 node , 对应每一个 tab 的子视窗是一个单独的 node , 完成子视窗的遍历以后, 就会选取 tab bar 本身 进行遍历

👍 👍 👍 ,已 star

互相学习, 欢迎 contribute 啊

后续计划配置几个比较有名的 app , 测试一下爬行效果

不错 又多一个优秀的项目 这块还能做的更好 自动遍历跟很多场景都可以有效结合

期待有一款优秀的 APP 遍历工具

谢谢前辈鼓励

我喜欢那个 report

也做了一个类似的东西。想请问一下,iOS 自动遍历保证不会重复点击的逻辑- -如果切换到别的页面,遍历完又返回到之前的页面后,怎么确定原页面的已被点击的元素

好厉害。
我这边试了下,启动示例 app 后,一直停留在登录页,并没有输入信息,请问是怎么回事呢?

1717p #36 回复

需要缓存已经爬取过的页面 ,下一次到这个页面的时候只点击没有触发的 element

xiaoshouzi #37 回复

具体原因具体分析了, 你也可以吧模拟器运行的 apk 或者 .app 给我我试试

就是测试用的示例 app,bootstrap

感谢楼主分享,回头试试看,和现有的其他自动遍历工具比较试试。

xiaoshouzi #41 回复

需要检查一下环境,以及使用的 yml 详细配置, 看下有没有具体的 log. 应该是环境问题

请问是说缓存整个树结构吗?

1717p #44 回复

恩 不是整个 tree, 是这个里面有效的信息模型

请问在 ios10 以上的版本,这个是否支持滑屏按照一定规则进行解锁呢? 目前已知 appium 的版本遍历工具是不支持

明白了,多谢。等周末具体读一下:)
目前做法类似,取树中的 key 做处理来做 key。但是有一个问题我没有想明白。如果想优化执行速度,从哪几个方面来入手比较好。目前想过的有缓存页面元素树,但是不像 Android 可以拿到对应的 activity,没有太好的头绪,请问有这样的想法吗?请教一下

卡斯 #46 回复

要自己设定操作了. 目前还没有支持到这个功能上来. 不过可以通过复写 performAction 这个函数实现

1717p #55 回复

有兴趣的话 欢迎一起做这个项目,窗口识别的问题, 目前通过实现一个窗口 digest 的方式去比对, 不过生成 digest 的函数现在也暴露出来, 可以自己定制, 比如说支持模糊度识别等等

已 fork
周末读代码学习一下 哈哈:)

@Samuel.ZhaoY 我也在做类似这样的工作,你说的通过窗口 digest 的方式去对比,有详细的策略吗?可以加你讨论下不 哈?

huang #51 回复

欢迎 contribute 以及提 issue.

匿名 #53 · 2017年09月18日

mark 太棒了 问个问题 就是这个深度遍历算法 是广度优先还是深度优先

目前做的 深度优先.

匿名 #47 · 2017年09月20日

xctestwd 啥时候能解决下 查找元素 id 为中文时乱码的 BUG 呀

你好,我最近也在做 ios 自动化遍历测试,过程中碰到一些问题:
1 app 在申请权限的时候,客户端无法给 appium 发送消息
2 有些界面弹出对话框,比如取消和确定,点击确定变成取消,用 curl post 消息的形式点击坐标也是点击的坐标为确定,实际点击为取消,ios 底层还是 app 应用对这些操作做了限制 ,这样会导致遍历一直退不出来的情况

我的思路和你的是相反的,采用递归来实现,但是难点就在于递归的层次和返回页面层次要保持一致。
iso 遍历 2 小时左右就已经很烫了,还会出现自动关机的情况,设置遍历的深度和时间很有必要。

匿名 #58 · 2017年09月21日
jeky2017 #57 回复

感觉如果是递归的话 不太符合用户的操作的习惯吧

这点很有道理~~

jeky2017 #57 回复

没错, 需要设置时间,最大深度,每个页面对多元素数量, 并且对元素类型进行筛选, 可以有效的提高爬行效率 减少运算成本

抱歉, 公司业务太忙, 还要搞 nosmoke, 会尽快看的 请谅解

使用 sudo 执行了 npm i macaca_ios -g 还是提示无权限

error: Unable to create directory: /usr/local/lib/node_modules/macaca-ios/node_modules/devicelog/build/devicelog.build/Release/devicelog.build (Permission denied)

【求救】

匿名 #39 · 2017年09月25日

我在 github 上回复了 我目前的暂时做法

请问 web 系统的遍历如何做?能否给个例子!

不错,可以试试看。

匿名 #67 · 2017年10月10日

cd /usr/local/lib/node_modules/macaca-ios
然后 chomd -R 777

可以支持真机吗,我在后面加了 ‘udid’ 参数,然后调用不起来

@Samuel.ZhaoY

我今天试了下,发现两个问题 已经报了 issue 帮我看下 https://github.com/macacajs/NoSmoke/issues/28

@Samuel.ZhaoY https://github.com/macacajs/NoSmoke/issues/27
我看这个人的问题 和我一样啊 也是唤起了测试 app 但执行不了 输入 那以后的步骤

-----> Crawling Finished <-----
(node:4352) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: socketed.disconne
cted is not a function


想问下配置的写法:
targetElements:
loginAccount:
actionType : 'action type: 1-click; 2-input'
searchValue : 'the value to search'
actionValue : 'the value to input'

我看你的样例 和 配置写法 又实际看了下测试 app 的 元素 不清楚 你配置里的 loginAccount 是怎么定位的 为什么写 loginAccount

targetElements: 是指被操作元素的话 但这里看不出 你后边写 loginAccount 对应的事 app-inspector 里的哪个元素。能解释下吗?
@Samuel.ZhaoY

安卓好像跑不起来啊??

按照默认配置修改,android 跑不起来,只显示一个 report 页面,页面上一只猴子😂

plateau #73 回复

麻烦试试最新的代码, 之前驱动有些不稳定

rockyrock #72 回复

试试现在的代码, 使用模拟器

梦梦GO #68 回复

目前模拟器比较稳定

老马 #71 回复

loginAccount 这层只是用来语义化 特定元素的,没有特别数据结构上的含义

您好,试了最新的代码,执行 npm run dev 命令后,就跳转到 report 页面,页面是空白的 没有 app 调起

@Samuel.ZhaoY android 能跑起来了,启动 app 后不会输入帐号密码登录进去 一直在登录界面

-----> Crawling Finished <-----
(node:19628) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id:
1): TypeError: process.exist is not a function

运行起来很快就结束了 没有输入进行任何操作

@Samuel.ZhaoY 例子跑通了,换用自己的 app 去跑,出现以下错误,请帮忙看看,谢谢

socket connected
(node:23480) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id:
2): FetchError: invalid json response body at http://localhost:3456/wd/hub/session reason:

Unexpected token I in JSON at position 0

UIAutomatorWD http server ready
responseHandler.js:56:12 [master] pid:24084 Send Error Respone to Client: Error: Comman
d failed: C:\gaoyuan\sdk/platform-tools/adb -s emulator-5554 install -r "C:\Users\YOYO.ma
caca-temp\amap_2.8.2.1018_publish_signed_201711080850_82ac783355b740ac984a05b34a53e3eae894
8d05_auto.apk"
Failed to install C:\Users\YOYO.macaca-temp\amap_2.8.2.1018_publish_signed_201711080850_8
2ac783355b740ac984a05b34a53e3eae8948d05_auto.apk: Failure [INSTALL_FAILED_NO_MATCHING_ABIS
: Failed to extract native libraries, res=-113]

responseHandler.js:62:14 [master] pid:24084 Error: Command failed: C:\gaoyuan\sdk/platf
orm-tools/adb -s emulator-5554 install -r "C:\Users\YOYO.macaca-temp\amap_2.8.2.1018_publ
ish_signed_201711080850_82ac783355b740ac984a05b34a53e3eae8948d05_auto.apk"
Failed to install C:\Users\YOYO.macaca-temp\amap_2.8.2.1018_publish_signed_201711080850_8
2ac783355b740ac984a05b34a53e3eae8948d05_auto.apk: Failure [INSTALL_FAILED_NO_MATCHING_ABIS
: Failed to extract native libraries, res=-113]

at ChildProcess.exithandler (child_process.js:198:12)
at emitTwo (events.js:106:13)
at ChildProcess.emit (events.js:191:7)
at maybeClose (internal/child_process.js:920:16)
at Process.ChildProcess._handle.onexit (internal/child_process.js:230:5)

Error: Command failed: C:\gaoyuan\sdk/platform-tools/adb -s emulator-5554 install -r "C:
\Users\YOYO.macaca-temp\amap_2.8.2.1018_publish_signed_201711080850_82ac783355b740ac984a0
5b34a53e3eae8948d05_auto.apk"
Failed to install C:\Users\YOYO.macaca-temp\amap_2.8.2.1018_publish_signed_201711080850
_82ac783355b740ac984a05b34a53e3eae8948d05_auto.apk: Failure [INSTALL_FAILED_NO_MATCHING_AB
: Failed to extract native libraries, res=-113]

at ChildProcess.exithandler (child_process.js:198:12)
at emitTwo (events.js:106:13)
at ChildProcess.emit (events.js:191:7)
at maybeClose (internal/child_process.js:920:16)
at Process.ChildProcess._handle.onexit (internal/child_process.js:230:5)

plateau #82 回复

你安装的 apk 文件使用了.so 文件, 你使用的安卓模拟器架构与 .so 支持的模拟器架构不符 https://stackoverflow.com/questions/24572052/install-failed-no-matching-abis-when-install-apk. 适配一下你的模拟器架构

能开放下 Dockerfile 文件?docker 中运行后,reports 中只看到了截图,html 报告怎么生成?另外登陆如果是 webview 怎么处理,有没有相应的操作?还有测试下来,提供的用例在全屏手机上 login 操作不了。

@Samuel.ZhaoY 可以指导下
permissionPatterns: '[\"继续安装\",\"下一步\",\"好\",\"允许\",\"确定\",\"我知道\"]'

该句的 具体源码路径 和原理吗? 这个很有用

老马 #17 回复

这个能力属于 macaca 安卓驱动 desiredcapability 的能力, 为了解决安装过程中 授权页面窗口定制化, 阻挡了安装使用而设计的. 具体源码要参考 uiautomator 里面, 唤起安卓测试驱动以后的一段代码. 通过 NoSmoke -> macaca-android -> uiautomator

这个有实现白名单和黑名单了吗

思寒_seveniruby IDEA debug scala sbt project AppCrawler 中提及了此贴 01月18日 16:31

android 的例子用模拟器可以测试,但是用真机在登陆页面自动填充了账号和密码后,就不懂了,过一会就提示超时结束

请问,我在做安卓的 monkey test, 页面很长,只能获取当前屏幕元素然后逐个递归,如果用手从下往上滑动,还有很多元素不能正确被获取到,换句话说,似乎只能获取当前屏幕上能够看的到的所有 Node, 而不是当前页面,只有把下面的页面通过手滑的操作显示到屏幕 上,才能获取下面的所有 Node, 这种怎么处理呢? 谢谢

进击的程序茗 Macaca-NoSmoke 遍历调研过程记录 中提及了此贴 07月03日 09:29

您好,想问下 Web 端的便利在 crawler.config.yml 是如何配置的,我看里面是有移动端的配置

9楼 已删除

想问一下 nosmoke 支持与数据库操作吗

/** 1.1.2 if finished browsing, and the current one is originates from a normal view, trigger back and then crawl again*/
this.repeatingCrawlingCount++;
if (this.currentNode.depth === 0) {
/** 1.1.2.1 if depth is 0 , then terminate crawling, avoid further navigate back /
this.repeatingCrawlingCount = maxRepeatCrawlingCount;
this.crawl();
} else {
/
* 1.1.2.2 if depth is not 0, then back and further explore */
console.log('=====this.curentNode.depth !== 0 back');
this.back();
}
}

想请问一下大佬 为什么 this.currentNode.depth ===0 的时候结束爬行啊 depth 不应该是自己设置的吗?

simple 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 12月13日 14:44
simple [精彩盘点] TesterHome 社区 2018 年 度精华帖 中提及了此贴 01月07日 12:08

macaca 支持 web 端的自动遍历嘛~~·

支持 iOS 真机吗?

@Samuel.ZhaoY Web 端的遍历在 crawler.config.yml 是如何配置的?

还有人在用吗?有人用到 web 端的遍历吗?

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册