multi-automator
Version:
Multi terminal automation
276 lines (275 loc) • 8.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
* @desc: android Handler
* @author: john_chen
* @date: 2025.03.02
*/
const fs_1 = require("fs");
const xmldom_1 = require("xmldom");
const xpath = __importStar(require("xpath"));
const adb = __importStar(require("./adb"));
const Atx_1 = __importDefault(require("./Atx"));
const env_1 = require("./env");
const config_1 = require("../config");
const Element_1 = __importDefault(require("./Element"));
class AndroidHandler {
constructor(deviceId, connectType) {
this.id = deviceId;
this.connectType = connectType;
this.atx = null;
this.adb = adb;
}
/**
* 初始化
*/
async init() {
config_1.logger.info('[android.Handler.init]');
await (0, env_1.checkEnv)(this.id);
this.atx = new Atx_1.default({ deviceId: this.id, connectType: this.connectType });
await this.atx.init();
}
/**
* 关闭
*/
async close() {
if (this.atx) {
await this.atx.close();
}
}
/**
* 返回主页
*/
async home() {
await this.adb.home(this.id);
}
/**
* 打开网页
*/
async goto(url) {
await this.adb.startActivity(this.id, {
data: url
});
}
/**
* 安装 APP
*/
async installApp(appPath) {
return await this.adb.install(this.id, appPath);
}
/**
* 卸载 APP
*/
async uninstallApp(appId) {
return await this.adb.uninstall(this.id, appId);
}
/**
* 检查 APP 是否安装
*/
async isInstalled(packageName) {
return await this.adb.isInstalled(this.id, packageName);
}
/**
* 启动 APP
*/
async launchApp(packageName, activity) {
let isAppInstalled = await this.adb.isInstalled(this.id, packageName);
if (!isAppInstalled) {
throw new Error(`APP is not installed: ${packageName}`);
}
if (!activity) {
if (!this.atx) {
throw new Error('ATX not initialized');
}
const info = await this.atx.packageInfo(packageName);
if (!info) {
throw new Error(`can not get app info: ${packageName}`);
}
activity = info.activity || '';
if (!activity) {
throw new Error(`can not get launch activity: ${packageName}`);
}
}
await this.adb.startActivity(this.id, {
wait: true,
action: 'android.intent.action.MAIN',
category: 'android.intent.category.LAUNCHER',
component: `${packageName}/${activity}`
});
config_1.logger.info(`[android.Handler.launchApp] ${packageName}/${activity}`);
}
async terminateApp(packageName) {
let isAppInstalled = await this.adb.isInstalled(this.id, packageName);
if (!isAppInstalled) {
throw new Error(`APP is not installed: ${packageName}`);
}
let res = await this.adb.isAppProcessExist(this.id, packageName);
if (res) {
await this.adb.killAppProcess(this.id, packageName);
}
config_1.logger.info(`[android.Handler.terminateApp] ${packageName}`);
}
/**
* 获取 APP 列表
*/
async appList() {
if (!this.atx) {
throw new Error('ATX not initialized');
}
return await this.atx.packages();
}
/**
* 获取 APP 信息
*/
async appInfo(packageName) {
if (!this.atx) {
throw new Error('ATX not initialized');
}
return await this.atx.packageInfo(packageName);
}
/**
* 获取设备信息
*/
async info() {
if (!this.atx) {
throw new Error('ATX not initialized');
}
return await this.atx.info();
}
/**
* 获取当前设备页面 dom 树
*/
async source(timeout = 20000) {
if (!this.atx) {
throw new Error('ATX not initialized');
}
return await this.atx.source(timeout);
}
/**
* 截图
*/
async screenshot(path) {
const res = await this.adb.screenshot(this.id);
if (path) {
(0, fs_1.writeFileSync)(path, res);
}
return res;
}
/**
* 获取屏幕尺寸
*/
async getScreenSize() {
return await this.adb.getScreenSize(this.id);
}
/**
* 点击操作
*/
async tap(x, y) {
let res = await this.adb.shell(this.id, `input tap ${x} ${y}`);
if (res.includes('Exception')) {
config_1.logger.error(`[android.Handler.tap] ${res}`);
return false;
}
config_1.logger.info(`[android.Handler.tap] ${x}, ${y}`);
return true;
}
/**
* 滑动操作
*/
async swipe(fx, fy, tx, ty, { duration } = { duration: 300 }) {
let cmd = `input swipe ${fx} ${fy} ${tx} ${ty} ${duration}`;
let res = await this.adb.shell(this.id, cmd);
config_1.logger.info(`[android.Handler.swipe] ${fx}, ${fy}, ${tx}, ${ty}, ${duration}`);
return res;
}
/**
* 长按操作
*/
async longpress(x, y, duration = 3000) {
let res = await this.swipe(x, y, x, y, { duration });
config_1.logger.info(`[android.Handler.longpress] ${x}, ${y}, ${duration}`);
return res;
}
/**
* 输入文本
*/
async input(text) {
const escapedText = text.replace(/([\\'\"` ])/g, '\\$1');
const cmd = `am broadcast -a ADB_INPUT_TEXT --es msg "${escapedText}"`;
let res = await this.adb.shell(this.id, cmd);
config_1.logger.info(`[android.Handler.input] ${res}`);
return res;
}
async $x(expression) {
const xml = await this.source();
const doc = new xmldom_1.DOMParser().parseFromString(xml, 'text/xml');
const nodes = xpath.select(expression, doc);
const screenSize = await this.getScreenSize();
const { height, statusBarHeight = 0 } = screenSize;
const elements = nodes.map(node => {
const boundsAttr = node.attributes.getNamedItem('bounds');
if (!boundsAttr || !boundsAttr.nodeValue) {
throw new Error('Bounds attribute is missing or invalid');
}
const bounds = this.parseBounds(boundsAttr.nodeValue);
if (bounds[3] === height - statusBarHeight) {
bounds[3] = height;
}
return new Element_1.default({
device: this,
bounds: {
x: bounds[0],
y: bounds[1],
width: bounds[2] - bounds[0],
height: bounds[3] - bounds[1]
},
attributes: node.attributes
});
});
return elements;
}
/**
* 回车
*/
async enter() {
return await this.adb.enter(this.id);
}
/**
* 返回
*/
async back() {
return await this.adb.back(this.id);
}
parseBounds(boundsString) {
const arrSplit = boundsString.slice(1, -1).split('][');
const bounds = arrSplit[0].split(',').concat(arrSplit[1].split(','));
return bounds.map(item => parseInt(item, 10));
}
}
exports.default = AndroidHandler;