UNPKG

multi-automator

Version:
276 lines (275 loc) 8.6 kB
"use strict"; 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;