rubick
Version:
tools for mtc
394 lines (367 loc) • 13.2 kB
JavaScript
/**
* 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;