UNPKG

rubick

Version:

tools for mtc

394 lines (367 loc) 13.2 kB
/** * Created by hongzhiyuan on 2017/4/14. */ "use strict" let co = require('co'); let fs = require('fs'); let bytes = require('bytes'); let adbClient = require('../../utils/adb'); let sleep = require('co-sleep'); let extend = require('extend'); let tempFile = require('tempfile'); let xmlDom = require('xmldom'); let domUtil = require('../../utils/dom'); let cmdUtil = require('../../utils/cmd'); let logger = require('../../log').init('rubick'); let Device = require('./base'); /** * Class AndroidDevice */ class AndroidDevice extends Device { /** * Constructs * * @param {string} serial: serial number of device * @param {string} adbStatus: adb status of device */ constructor(serial, adbStatus) { super(serial, adbStatus); this.os = 'android'; } /** * initialization */ init() { this.nodes = []; this.nodeMap = {}; this.properties = []; this.features = []; this.resolution = { width: -1, height: -1 }; this.model = 'unknow'; this.brand = 'unknow'; this.name = 'unknow'; this.manufacturer = 'unknow'; this.sdk = -1; this.version = 'unknow'; this.rom = {os: 'other'}; this.nodeIndex = 0; } /** * install app on device * * @param apk {string} url * * @return {Promise} promise */ installApk(apk) { logger.info(`android device ${this.serial} install ${apk}`); return new Promise((resolve, reject) => { adbClient.install(this.serial, apk).then(() => { logger.info(`install ${apk} success`); resolve(true); }, reject); // let tempApkFile = tempFile('.apk'); // let request = require('request'); // logger.debug(`download ${apk} to ${tempApkFile}`); // let stream = request(apk).pipe(fs.createWriteStream(tempApkFile)); // stream.on('finish', () => { // logger.info(`download ${apk} success`); // adbClient.install(this.serial, tempApkFile).then(() => { // logger.info(`install ${apk} success`); // resolve(); // }, reject); // }); // stream.on('error', (err) => { // logger.debug(`download ${apk} fail`); // reject(err); // }); }); } analysisApk(path) { logger.debug(`analysis apk file start`); return co(function* (self) { yield cmdUtil.exec('aapt d badging ' + path).then((stdout) => { let result = []; console.log(stdout); return stdout; }); return true; }(this)).catch((e) => { logger.error(`read file error` + e.message); return ''; }); } /** * tap * * @param {Object} position: relative position, {x: 0.0 ~ 1.0, y: 0.0 ~ 1.0} * @return {thunks} trunk */ tap(position) { logger.debug(`local android device tap ${this.host}::${this.serial}`); return co(function* (self) { let x = Math.floor(position.x); let y = Math.floor(position.y); yield adbClient.shell(self.serial, 'input tap ' + x + ' ' + y); return true; }(this)).catch((e) => { logger.error(`android device ${this.serial} tap error: ${e.message}`); return false; }); } input(text) { logger.debug(`local android device input ${this.host}::${this.serial}`); return co(function* (self) { yield adbClient.shell(self.serial, 'input text "' + text + '"'); return true; }(this)).catch((e) => { logger.error(`android device ${this.serial} input error: ${e.message}`); return false; }); } /** * drag * * @param {Object} option: * { * fromX: 0.0 ~ 1.0, fromY: 0.0 ~ 1.0, * toX: 0.0 ~ 1.0, toY: 0.0 ~ 1.0 * } * @return {thunks} trunk */ drag(option) { logger.debug(`local android device drag ${this.host}::${this.serial}`); return co(function* (self) { let fromX = Math.floor(option.fromX); let fromY = Math.floor(option.fromY); // let fromY = Math.floor(self.resolution.height * option.fromY); let toX = Math.floor(option.toX); let toY = Math.floor(option.toY); // let toY = Math.floor(self.resolution.height * option.toY); yield adbClient.shell(self.serial, 'input swipe ' + fromX + ' ' + fromY + ' ' + toX + ' ' + toY); return true; }(this)).catch((e) => { logger.error(`android device ${this.serial} drag error: ${e.message}`); return false; }); } dumpDom() { return co(function* (self) { let text = yield self.dump(); // return text; let doc = new xmlDom.DOMParser().parseFromString(text,'text/xml'); if (doc == null) { return {'nodeTree': {}, 'nodeObj': {}}; } let root = doc.documentElement; let nodeList = root.getElementsByTagName('node'); self.nodes = []; let rootNode = { 'element': {}, 'children': [], 'parentId': -1, }; self.nodeIndex = 0; self.loopNode(nodeList[0], rootNode); logger.info('loop nodeList ends'); let nodeMap = {}; for (let i=0; i<self.nodes.length; i++) { nodeMap[self.nodes[i].element.id] = self.nodes[i]; } return {'nodeTree': rootNode, 'nodeObj': nodeMap}; }(this)).catch((e) => { logger.error(`android device ${this.serial} dumpDom error: ${e.message}`); return ''; }); } loopNode(node, parent) { parent.element = domUtil.parseNode(node, this.nodeIndex); parent.children = []; this.nodes.push({'element': parent.element, 'parentId': parent.parentId}); let child_arr = node.childNodes; for(let i = 0; i < child_arr.length; i++) { parent.children.push({'element': {}, 'parentId': parent.element.id}); // logger.info('loop node index is:' + this.nodeIndex); this.nodeIndex++; this.loopNode(child_arr[i], parent.children[i]); } } dump() { logger.debug(`local android device dumpview ${this.host}::${this.serial}`); return co(function* (self) { let viewPath = yield self.dumpView(); logger.debug(viewPath); let pullResult = ''; if (viewPath === null || viewPath === '') { pullResult = ''; } else { pullResult = yield self.pull(viewPath); } let text = ''; if (pullResult !== '') { text = fs.readFileSync(pullResult, 'utf8'); } return text; }(this)).catch((e) => { logger.error(`android device ${this.serial} dump error: ${e.message}`); return ''; }); } dumpView() { return adbClient.shell(this.serial, 'uiautomator dump') .then((output) => { let match = output.toString('utf-8').match(/\/.*\.xml/); if (match) { return match[0]; } return ''; }); } pull(viewPath) { let tempXML = tempFile('.xml'); return adbClient.pull(this.serial, viewPath, tempXML) .then((output) => { return tempXML; // let match = output.toString('utf-8').match(/100%/); // if (match) { // return tempXML; // } // return ''; }); } /** * home button * * @return {thunks} trunk */ home() { return this.sendKey(3); } /** * send keyevent * * @param {integer} key: key code * @return {thunks} trunk */ sendKey(key) { logger.debug(`local android device sendkey ${this.host}::${this.serial}`); return co(function* (self) { yield adbClient.shell(self.serial, 'input keyevent ' + key); return true; }(this)).catch((e) => { logger.error(`android device ${this.serial} sendkey error: ${e.message}`); return false; }); } _sendInputKeyEvent(key) { logger.debug(`local android device send input keyevent ${this.host}::${this.serial}`); return co(function* (self) { yield adbClient.shell(self.serial, 'input keyevent ' + key); return true; }(this)).catch((e) => { logger.error(`android device ${this.serial} send input keyevent error: ${e.message}`); return false; }); } reboot() { logger.debug(`local device reboot ${this.host}::${this.serial}`); return co(function* (self) { return yield adbClient.reboot(self.serial); }(this)).catch((e) => { logger.error(`device ${this.serial} reboot error: ${e.message}`); }); } screenShot() { logger.debug(`local device screenshot ${this.serial}`); // let sharp = require('sharp'); // let resizeTransform = sharp().toFormat('jpeg').on('error', (err) => { // logger.warn(`local device ${this.serial} screenshot idevicescreenshot:5_sharp because ${err}`); // }).quality(95); return new Promise((resolve, reject) => { adbClient.screencap(this.serial).then((stream) => { try { // let outputStream = stream.pipe(resizeTransform); resolve(stream); } catch (err) { logger.debug(`local device ${this.serial} screenshot idevicescreenshot:4 because ${err}`); reject(err); } }, reject); }); // return adbClient.screencap(this.serial); } deviceOperate(data) { let self = this; if (data != null) { if (data.type === 'click') { return self.tap(data.data.pos); } else if (data.type === 'swipe') { let startPos = data.data.startPos; let endPos = data.data.endPos; let option = {fromX:startPos.x, fromY:startPos.y, toX: endPos.x, toY: endPos.y}; return self.drag(option); } else if (data.type === 'homeClick') { return self.sendKey(3); } else if (data.type === 'backClick') { return self.sendKey(4); } else if (data.type === 'menuClick') { return self.sendKey(1); } else if (data.type === 'input') { return self.input(data.data.text); } else { return self.sendKey(0); } } } _getResolution() { if (this.sdk <= 17) { return this._getResolutionFallback(); } return adbClient.shell(this.serial, 'wm size') .then((output) => { let match = output.toString('utf-8').match(/.*:\s*(\d+)x(\d+)/); if (match) { return { width: parseInt(match[1], 10), height: parseInt(match[2], 10) }; } return { width: -1, height: -1 }; }); } _getResolutionFallback() { return adbClient.shell(this.serial, 'dumpsys window') .then((output) => { let match = output.toString('utf-8').match(/mUnrestrictedScreen=\(0,0\)\s*(\d+)x(\d+)/); if (match) { return { width: parseInt(match[1], 10), height: parseInt(match[2], 10) }; } return { width: -1, height: -1 }; }); } toJSON() { return extend(true, super.toJSON(), { unique_id: this.unique_id, adbStatus: this.adbStatus, sdk: this.sdk, rom: this.rom, features: this.features, preAppState: this.preAppState }); } } module.exports = AndroidDevice;