hdckit
Version:
A pure Node.js client for the OpenHarmony Device Connector
330 lines (329 loc) • 12.5 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const trim_1 = __importDefault(require("licia/trim"));
const contain_1 = __importDefault(require("licia/contain"));
const cmpVersion_1 = __importDefault(require("licia/cmpVersion"));
const toNum_1 = __importDefault(require("licia/toNum"));
const getPort_1 = __importDefault(require("licia/getPort"));
const strHash_1 = __importDefault(require("licia/strHash"));
const now_1 = __importDefault(require("licia/now"));
const sleep_1 = __importDefault(require("licia/sleep"));
const node_net_1 = __importDefault(require("node:net"));
const Emitter_1 = __importDefault(require("licia/Emitter"));
const singleton_1 = __importDefault(require("licia/singleton"));
const SDK_PATH = path_1.default.join(__dirname, '../../uitestkit_sdk/uitest_agent_v1.1.0.so');
const SDK_VERSION = '1.1.0';
const AGENT_PATH = '/data/local/tmp/agent.so';
class UiDriver extends Emitter_1.default {
constructor(target, sdkPath, sdkVersion) {
super();
this.connection = null;
this.driverName = '';
this.port = 0;
this.sdkVersion = SDK_VERSION;
this.sdkPath = SDK_PATH;
this.triedStarting = false;
this.captureScreenCallback = null;
this.send = async (method, api, args = {}) => {
try {
const connection = await this.getConnection();
const module = 'com.ohos.devicetest.hypiumApiHelper';
if (method === 'callHypiumApi') {
return connection.sendMessage({
module,
method,
params: {
api: `Driver.${api}`,
this: this.driverName,
args,
message_type: 'hypium',
},
});
}
return connection.sendMessage({
module,
method,
params: {
api,
args,
},
});
}
catch (e) {
if (e.message === 'timeout' && !this.triedStarting) {
this.triedStarting = true;
await this.stop();
return await this.send(method, api, args);
}
else {
throw e;
}
}
};
this.getConnection = (0, singleton_1.default)(async () => {
let { connection } = this;
if (!connection) {
await this.start();
connection = new Connection();
connection.setOnMessage((sessionId, message) => {
this.emit('message', sessionId, message);
});
await connection.connect(this.port);
connection.socket.on('end', () => {
this.connection = null;
});
const { result } = await connection.sendMessage({
module: 'com.ohos.devicetest.hypiumApiHelper',
method: 'callHypiumApi',
params: {
api: 'Driver.create',
this: null,
args: [],
message_type: 'hypium',
},
}, 1000);
this.driverName = result;
this.connection = connection;
}
return connection;
});
this.target = target;
if (sdkPath) {
this.sdkPath = sdkPath;
}
if (sdkVersion) {
this.sdkVersion = sdkVersion;
}
}
async start() {
let uiTestPid = (0, trim_1.default)(await this.shell('pidof uitest'));
const shouldUpdateSdk = await this.shouldUpdateSdk();
if (!uiTestPid || shouldUpdateSdk) {
await this.shell('param set persist.ace.testmode.enabled 1');
if (shouldUpdateSdk) {
if (uiTestPid) {
await this.shell(`kill -9 ${uiTestPid}`);
uiTestPid = '';
}
await this.updateSdk();
}
if (!uiTestPid) {
await this.shell('uitest start-daemon singleness');
await (0, sleep_1.default)(2000);
}
}
this.port = await this.forwardTcp(8012);
}
async stop() {
if (this.connection) {
this.connection.end();
this.connection = null;
}
const uiTestPid = (0, trim_1.default)(await this.shell('pidof uitest'));
if (uiTestPid) {
await this.shell(`kill -9 ${uiTestPid}`);
}
}
async startCaptureScreen(callback, options = { scale: 1 }) {
if (options.scale >= 1 || options.scale <= 0) {
delete options.scale;
}
const { sessionId } = await this.send('Captures', 'startCaptureScreen', {
options,
});
if (this.captureScreenCallback) {
throw new Error('Capture screen is already started');
}
this.captureScreenCallback = (id, message) => {
if (id === sessionId) {
callback(message);
}
};
this.on('message', this.captureScreenCallback);
}
async stopCaptureScreen() {
await this.send('Captures', 'stopCaptureScreen');
if (this.captureScreenCallback) {
this.off('message', this.captureScreenCallback);
this.captureScreenCallback = null;
}
}
captureLayout() {
return this.send('Captures', 'captureLayout').then(({ result }) => result);
}
getDisplaySize() {
return this.send('CtrlCmd', 'getDisplaySize').then(({ result }) => result);
}
async touchDown(x, y) {
await this.send('Gestures', 'touchDown', { x, y });
}
async touchMove(x, y) {
await this.send('Gestures', 'touchMove', { x, y });
}
async touchUp(x, y) {
await this.send('Gestures', 'touchUp', { x, y });
}
async inputText(text, x = 0, y = 0) {
await this.send('callHypiumApi', 'inputText', [{ x, y }, text]);
}
async forwardTcp(p) {
const { target } = this;
const remote = `tcp:${p}`;
const forwards = await target.listForwards();
for (let i = 0, len = forwards.length; i < len; i++) {
const forward = forwards[i];
if (forward.remote === remote) {
return (0, toNum_1.default)(forward.local.replace('tcp:', ''));
}
}
const port = await (0, getPort_1.default)();
const local = `tcp:${port}`;
await target.forward(local, remote);
return port;
}
async shouldUpdateSdk() {
const result = await this.shell(`cat ${AGENT_PATH} | grep -a UITEST_AGENT_LIBRARY`);
if (!(0, contain_1.default)(result, 'UITEST_AGENT_LIBRARY')) {
return true;
}
const deviceSdkVersion = getSdkVersion(result);
return (0, cmpVersion_1.default)(deviceSdkVersion, this.sdkVersion) < 0;
}
async updateSdk() {
await this.shell(`rm ${AGENT_PATH}`);
await this.target.sendFile(this.sdkPath, AGENT_PATH);
}
async shell(command) {
const connection = await this.target.shell(command);
return (await connection.readAll()).toString();
}
}
exports.default = UiDriver;
const HEADER_BYTES = Buffer.from('_uitestkit_rpc_message_head_');
const TAILER_BYTES = Buffer.from('_uitestkit_rpc_message_tail_');
class Connection {
constructor() {
this.ended = false;
this.resolves = new Map();
this.rejects = new Map();
this.buffer = Buffer.alloc(0);
this.onData = (data) => {
let buffer = this.buffer;
buffer = Buffer.concat([buffer, data]);
while (buffer.length >= HEADER_BYTES.length + 8) {
const headerBytes = buffer.subarray(0, HEADER_BYTES.length);
if (headerBytes.compare(HEADER_BYTES) !== 0) {
buffer = Buffer.alloc(0);
break;
}
if (buffer.length < HEADER_BYTES.length + 8) {
break;
}
const sessionId = buffer.readUInt32BE(HEADER_BYTES.length);
const len = buffer.readUInt32BE(HEADER_BYTES.length + 4);
const totalLength = HEADER_BYTES.length + 8 + len + TAILER_BYTES.length;
if (buffer.length < totalLength) {
break;
}
const start = HEADER_BYTES.length + 8;
const end = start + len;
const message = buffer.subarray(start, end);
const tailerBytes = buffer.subarray(end, end + TAILER_BYTES.length);
if (tailerBytes.compare(TAILER_BYTES) !== 0) {
buffer = Buffer.alloc(0);
break;
}
const resolve = this.resolves.get(sessionId);
const reject = this.rejects.get(sessionId);
if (resolve) {
try {
const result = JSON.parse(message.toString());
if (result.exception) {
reject(new Error(result.exception.message));
}
else {
resolve({ sessionId, result: result.result });
}
// eslint-disable-next-line
}
catch (e) {
resolve({ sessionId, result: message });
}
this.resolves.delete(sessionId);
this.rejects.delete(sessionId);
}
else if (this.onMessage) {
this.onMessage(sessionId, message);
}
buffer = this.buffer.subarray(totalLength);
}
this.buffer = buffer;
};
}
connect(port) {
const socket = node_net_1.default.connect({
host: '127.0.0.1',
port,
});
socket.setNoDelay(true);
this.socket = socket;
return new Promise((resolve, reject) => {
socket.once('connect', async () => {
socket.on('data', this.onData);
resolve(null);
});
socket.once('error', reject);
socket.once('end', () => {
this.ended = true;
this.socket = null;
});
});
}
setOnMessage(onMessage) {
this.onMessage = onMessage;
}
sendMessage(message, timeout = 0) {
message = JSON.stringify(message);
const sessionId = (0, strHash_1.default)((0, now_1.default)() + message);
const sessionIdBuf = Buffer.alloc(4);
sessionIdBuf.writeUInt32BE(sessionId, 0);
return new Promise((resolve, reject) => {
this.resolves.set(sessionId, resolve);
this.rejects.set(sessionId, reject);
this.sendRawMessage(sessionIdBuf, Buffer.from(message));
if (timeout) {
setTimeout(() => {
reject(new Error('timeout'));
this.resolves.delete(sessionId);
this.rejects.delete(sessionId);
}, timeout);
}
});
}
sendRawMessage(sessonId, message) {
if (this.ended) {
throw new Error('ended');
}
const buffers = [];
buffers.push(HEADER_BYTES);
buffers.push(sessonId);
const len = Buffer.alloc(4);
len.writeUInt32BE(message.length, 0);
buffers.push(len);
buffers.push(message);
buffers.push(TAILER_BYTES);
this.socket.write(Buffer.concat(buffers));
}
end() {
if (this.socket) {
this.socket.end();
}
}
}
function getSdkVersion(raw) {
return (0, trim_1.default)(raw.slice(raw.indexOf('@v') + 2));
}