目前在 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;