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

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

目前在 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 条回复 时间 点赞

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

老马 回复

稍等

老马 回复

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

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

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

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

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

Baozhida 回复

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

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

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

强烈关注!!

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

老马 回复

恩 会尽快完善

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

董文华 回复

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

董文华 回复

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

董文华 回复

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

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

孟德功 回复

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

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

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

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

0x88 回复

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

Samuel.ZhaoY 回复

👍 👍 👍 ,已 star

互相学习, 欢迎 contribute 啊

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

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

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

好的

谢谢前辈鼓励

我喜欢那个 report

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

Samuel.ZhaoY 回复

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

1717p 回复

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

xiaoshouzi 回复

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

Samuel.ZhaoY 回复

就是测试用的示例 app,bootstrap

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

xiaoshouzi 回复

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

Samuel.ZhaoY 回复

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

1717p 回复

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

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

Samuel.ZhaoY 回复

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

卡斯 回复

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

1717p 回复

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

Samuel.ZhaoY 回复

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

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

huang 回复

欢迎 contribute 以及提 issue.

匿名 #53 · 2017年09月18日

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

目前做的 深度优先.

匿名 #55 · 2017年09月20日
Samuel.ZhaoY 回复

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

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

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

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

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

这点很有道理~~

jeky2017 回复

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

抱歉, 公司业务太忙, 还要搞 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)

【求救】

匿名 #63 · 2017年09月25日
Samuel.ZhaoY 回复

我在 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 回复

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

rockyrock 回复

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

梦梦GO 回复

目前模拟器比较稳定

老马 回复

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

Samuel.ZhaoY 回复

您好,试了最新的代码,执行 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 回复

你安装的 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: '[\"继续安装\",\"下一步\",\"好\",\"允许\",\"确定\",\"我知道\"]'

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

老马 回复

这个能力属于 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
Samuel.ZhaoY 回复

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

93楼 已删除

想问一下 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 端的遍历吗?

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