目前在 macaca 提供的基础能力上研发出了一套多端深度遍历爬虫工具.  希望可以最大化减少 UI 测试脚本的编写
涵盖以下功能点:
轻度定制
中度定制
重度定制
项目源码地址:  https://github.com/macacajs/NoSmoke
macaca-reporter 源码地址:  https://github.com/macacajs/macaca-reporter
目前项目正在收尾阶段, 所以先发一个原型出来,希望收集一下大家的意见,发布版本会根据大家的意见进行调优.



项目依赖:
请确保你安装了 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
对于新手,建议直接使用 NoSmoke 项目中 public 路径下配置好的 yml 文件, 里面标注了 iOS/Android/Web-PC 三种爬取对象的简易配置, 可以直接跑起来
NoSmoke 根目录执行
macaca server --verbose
新开命令行窗口, 在 NoSmoke 根目录执行
npm run dev
 
  
# 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'
再精密设计的深度遍历算法对于不同的 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;
};
用户可以参考 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;
    
     