askui
Version:
Reliable, automated end-to-end-testing that depends on what is shown on your screen instead of the technology you are running on
138 lines (137 loc) • 6.52 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import WebSocket from 'ws';
import { logger } from '../../lib/logger';
import { UiControllerClientConnectionState } from '../ui-controller-client-connection-state';
import { LegacyControllerError } from './legacy-controller-error';
import { LegacyControllerNotConnectedError } from './legacy-controller-not-connected-error';
/**
* Drives an Android device through the AskUI legacy UI Controller over its
* WebSocket "runner protocol" (default `ws://127.0.0.1:6769`), implementing
* {@link DeviceClient} so it is interchangeable with the desktop AgentOS gRPC
* client and the direct-adb Android client.
*
* The controller must already be running in Android mode
* (`AskUI-StartController ... -r android`). The controller serves one command at
* a time, so requests are serialized.
*/
export class LegacyAndroidClient {
constructor(args = {}) {
var _a;
this.connectionState = UiControllerClientConnectionState.NOT_CONNECTED;
this.url = (_a = args.controllerUrl) !== null && _a !== void 0 ? _a : LegacyAndroidClient.DEFAULT_URL;
}
connect() {
this.connectionState = UiControllerClientConnectionState.CONNECTING;
return new Promise((resolve, reject) => {
const ws = new WebSocket(this.url);
const timeout = setTimeout(() => {
ws.terminate();
reject(new LegacyControllerError(`Connection to the AskUI legacy UI Controller at "${this.url}" timed out. `
+ 'Make sure it is running in Android mode (AskUI-StartController ... -r android).'));
}, LegacyAndroidClient.CONNECT_TIMEOUT_IN_MS);
ws.on('open', () => {
clearTimeout(timeout);
this.ws = ws;
this.connectionState = UiControllerClientConnectionState.CONNECTED;
logger.debug(`Connected to legacy UI Controller at ${this.url}`);
resolve(this.connectionState);
});
ws.on('message', (data) => this.onMessage(data));
ws.on('error', (error) => {
clearTimeout(timeout);
this.connectionState = UiControllerClientConnectionState.ERROR;
reject(new LegacyControllerError(`Connection to the AskUI legacy UI Controller at "${this.url}" cannot be established. `
+ 'Make sure it is running in Android mode (AskUI-StartController ... -r android). '
+ `Cause: ${error.message}`));
});
});
}
onMessage(data) {
var _a;
if (this.pending === undefined) {
return;
}
const { resolve, reject, timeout } = this.pending;
clearTimeout(timeout);
this.pending = undefined;
let response;
try {
response = JSON.parse(data.toString());
}
catch (error) {
reject(new LegacyControllerError(`Malformed response from the legacy UI Controller: ${error}`));
return;
}
if ((_a = response.data) === null || _a === void 0 ? void 0 : _a.error) {
reject(new LegacyControllerError(response.data.error));
return;
}
resolve(response);
}
disconnect() {
var _a;
(_a = this.ws) === null || _a === void 0 ? void 0 : _a.close();
this.ws = undefined;
this.connectionState = UiControllerClientConnectionState.NOT_CONNECTED;
}
sendAndReceive(request) {
const { ws } = this;
if (ws === undefined || this.connectionState !== UiControllerClientConnectionState.CONNECTED) {
throw new LegacyControllerNotConnectedError();
}
if (this.pending !== undefined) {
throw new LegacyControllerError('A request to the legacy UI Controller is already in flight. '
+ 'Requests must be serialized (check for a missing await).');
}
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
this.pending = undefined;
reject(new LegacyControllerError(`Request "${request.msgName}" to the legacy UI Controller timed out.`));
}, LegacyAndroidClient.REQUEST_TIMEOUT_IN_MS);
this.pending = { reject, resolve, timeout };
logger.debug(`Send to legacy UI Controller: ${request.msgName}`);
ws.send(JSON.stringify(request));
});
}
requestControl(controlCommand) {
return __awaiter(this, void 0, void 0, function* () {
yield this.sendAndReceive({
controlCommand: controlCommand.toJson(),
msgName: 'CONTROL_REQUEST',
});
});
}
requestScreenshot() {
return __awaiter(this, void 0, void 0, function* () {
const response = yield this.sendAndReceive({ msgName: 'CAPTURE_SCREENSHOT_REQUEST' });
if (response.data.image === undefined) {
throw new LegacyControllerError('The legacy UI Controller returned no screenshot.');
}
return response.data.image;
});
}
getStartingArguments() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const response = yield this.sendAndReceive({ msgName: 'GET_STARTING_ARGUMENTS_REQUEST' });
return (_a = response.data.arguments) !== null && _a !== void 0 ? _a : {};
});
}
// eslint-disable-next-line class-methods-use-this
setActiveDisplay() {
return __awaiter(this, void 0, void 0, function* () {
// The legacy controller selects its display/device when it is launched.
});
}
}
LegacyAndroidClient.DEFAULT_URL = 'ws://127.0.0.1:6769';
LegacyAndroidClient.REQUEST_TIMEOUT_IN_MS = 30000;
LegacyAndroidClient.CONNECT_TIMEOUT_IN_MS = 10000;