@sqaitech/android 0.30.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +9 -0
- package/bin/yadb +0 -0
- package/dist/es/index.mjs +996 -0
- package/dist/lib/index.js +1057 -0
- package/dist/types/index.d.ts +145 -0
- package/package.json +53 -0
|
@@ -0,0 +1,1057 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const __rslib_import_meta_url__ = /*#__PURE__*/ function() {
|
|
3
|
+
return 'undefined' == typeof document ? new (require('url'.replace('', ''))).URL('file:' + __filename).href : document.currentScript && document.currentScript.src || new URL('main.js', document.baseURI).href;
|
|
4
|
+
}();
|
|
5
|
+
var __webpack_require__ = {};
|
|
6
|
+
(()=>{
|
|
7
|
+
__webpack_require__.n = (module)=>{
|
|
8
|
+
var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
|
|
9
|
+
__webpack_require__.d(getter, {
|
|
10
|
+
a: getter
|
|
11
|
+
});
|
|
12
|
+
return getter;
|
|
13
|
+
};
|
|
14
|
+
})();
|
|
15
|
+
(()=>{
|
|
16
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
17
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
get: definition[key]
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
})();
|
|
23
|
+
(()=>{
|
|
24
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
25
|
+
})();
|
|
26
|
+
(()=>{
|
|
27
|
+
__webpack_require__.r = (exports1)=>{
|
|
28
|
+
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
29
|
+
value: 'Module'
|
|
30
|
+
});
|
|
31
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
32
|
+
value: true
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __webpack_exports__ = {};
|
|
37
|
+
__webpack_require__.r(__webpack_exports__);
|
|
38
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
39
|
+
getConnectedDevices: ()=>getConnectedDevices,
|
|
40
|
+
agentFromAdbDevice: ()=>agentFromAdbDevice,
|
|
41
|
+
AndroidDevice: ()=>AndroidDevice,
|
|
42
|
+
overrideAIConfig: ()=>env_namespaceObject.overrideAIConfig,
|
|
43
|
+
AndroidAgent: ()=>AndroidAgent
|
|
44
|
+
});
|
|
45
|
+
const external_node_assert_namespaceObject = require("node:assert");
|
|
46
|
+
var external_node_assert_default = /*#__PURE__*/ __webpack_require__.n(external_node_assert_namespaceObject);
|
|
47
|
+
const external_node_fs_namespaceObject = require("node:fs");
|
|
48
|
+
var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
|
|
49
|
+
const external_node_module_namespaceObject = require("node:module");
|
|
50
|
+
const external_node_path_namespaceObject = require("node:path");
|
|
51
|
+
var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
|
|
52
|
+
const core_namespaceObject = require("@sqaitech/core");
|
|
53
|
+
const device_namespaceObject = require("@sqaitech/core/device");
|
|
54
|
+
const utils_namespaceObject = require("@sqaitech/core/utils");
|
|
55
|
+
const env_namespaceObject = require("@sqaitech/shared/env");
|
|
56
|
+
const img_namespaceObject = require("@sqaitech/shared/img");
|
|
57
|
+
const logger_namespaceObject = require("@sqaitech/shared/logger");
|
|
58
|
+
const shared_utils_namespaceObject = require("@sqaitech/shared/utils");
|
|
59
|
+
const external_appium_adb_namespaceObject = require("appium-adb");
|
|
60
|
+
function _define_property(obj, key, value) {
|
|
61
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
62
|
+
value: value,
|
|
63
|
+
enumerable: true,
|
|
64
|
+
configurable: true,
|
|
65
|
+
writable: true
|
|
66
|
+
});
|
|
67
|
+
else obj[key] = value;
|
|
68
|
+
return obj;
|
|
69
|
+
}
|
|
70
|
+
const defaultScrollUntilTimes = 10;
|
|
71
|
+
const defaultFastScrollDuration = 100;
|
|
72
|
+
const defaultNormalScrollDuration = 1000;
|
|
73
|
+
const IME_STRATEGY_ALWAYS_YADB = 'always-yadb';
|
|
74
|
+
const IME_STRATEGY_YADB_FOR_NON_ASCII = 'yadb-for-non-ascii';
|
|
75
|
+
const debugDevice = (0, logger_namespaceObject.getDebug)('android:device');
|
|
76
|
+
class AndroidDevice {
|
|
77
|
+
actionSpace() {
|
|
78
|
+
const defaultActions = [
|
|
79
|
+
(0, device_namespaceObject.defineActionTap)(async (param)=>{
|
|
80
|
+
const element = param.locate;
|
|
81
|
+
external_node_assert_default()(element, 'Element not found, cannot tap');
|
|
82
|
+
await this.mouseClick(element.center[0], element.center[1]);
|
|
83
|
+
}),
|
|
84
|
+
(0, device_namespaceObject.defineActionDoubleClick)(async (param)=>{
|
|
85
|
+
const element = param.locate;
|
|
86
|
+
external_node_assert_default()(element, 'Element not found, cannot double click');
|
|
87
|
+
await this.mouseDoubleClick(element.center[0], element.center[1]);
|
|
88
|
+
}),
|
|
89
|
+
(0, device_namespaceObject.defineAction)({
|
|
90
|
+
name: 'Input',
|
|
91
|
+
description: 'Input text into the input field',
|
|
92
|
+
interfaceAlias: 'aiInput',
|
|
93
|
+
paramSchema: core_namespaceObject.z.object({
|
|
94
|
+
value: core_namespaceObject.z.string().describe('The text to input. Provide the final content for replace/append modes, or an empty string when using clear mode to remove existing text.'),
|
|
95
|
+
autoDismissKeyboard: core_namespaceObject.z.boolean().optional().describe('If true, the keyboard will be dismissed after the input is completed. Do not set it unless the user asks you to do so.'),
|
|
96
|
+
mode: core_namespaceObject.z["enum"]([
|
|
97
|
+
'replace',
|
|
98
|
+
'clear',
|
|
99
|
+
'append'
|
|
100
|
+
]).default('replace').optional().describe('Input mode: "replace" (default) - clear the field and input the value; "append" - append the value to existing content; "clear" - clear the field without inputting new text.'),
|
|
101
|
+
locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The input field to be filled').optional()
|
|
102
|
+
}),
|
|
103
|
+
call: async (param)=>{
|
|
104
|
+
var _this_options;
|
|
105
|
+
const element = param.locate;
|
|
106
|
+
if (element) {
|
|
107
|
+
if ('append' !== param.mode) await this.clearInput(element);
|
|
108
|
+
}
|
|
109
|
+
if ('clear' === param.mode) return;
|
|
110
|
+
if (!param || !param.value) return;
|
|
111
|
+
const autoDismissKeyboard = param.autoDismissKeyboard ?? (null == (_this_options = this.options) ? void 0 : _this_options.autoDismissKeyboard);
|
|
112
|
+
await this.keyboardType(param.value, {
|
|
113
|
+
autoDismissKeyboard
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}),
|
|
117
|
+
(0, device_namespaceObject.defineActionScroll)(async (param)=>{
|
|
118
|
+
const element = param.locate;
|
|
119
|
+
const startingPoint = element ? {
|
|
120
|
+
left: element.center[0],
|
|
121
|
+
top: element.center[1]
|
|
122
|
+
} : void 0;
|
|
123
|
+
const scrollToEventName = null == param ? void 0 : param.scrollType;
|
|
124
|
+
if ('untilTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
125
|
+
else if ('untilBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
126
|
+
else if ('untilRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
127
|
+
else if ('untilLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
128
|
+
else if ('once' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
129
|
+
else {
|
|
130
|
+
if ((null == param ? void 0 : param.direction) !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
|
|
131
|
+
else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
|
|
132
|
+
else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
|
|
133
|
+
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
134
|
+
else await this.scrollDown((null == param ? void 0 : param.distance) || void 0, startingPoint);
|
|
135
|
+
await (0, utils_namespaceObject.sleep)(500);
|
|
136
|
+
}
|
|
137
|
+
}),
|
|
138
|
+
(0, device_namespaceObject.defineActionDragAndDrop)(async (param)=>{
|
|
139
|
+
const from = param.from;
|
|
140
|
+
const to = param.to;
|
|
141
|
+
external_node_assert_default()(from, 'missing "from" param for drag and drop');
|
|
142
|
+
external_node_assert_default()(to, 'missing "to" param for drag and drop');
|
|
143
|
+
await this.mouseDrag({
|
|
144
|
+
x: from.center[0],
|
|
145
|
+
y: from.center[1]
|
|
146
|
+
}, {
|
|
147
|
+
x: to.center[0],
|
|
148
|
+
y: to.center[1]
|
|
149
|
+
});
|
|
150
|
+
}),
|
|
151
|
+
(0, device_namespaceObject.defineActionKeyboardPress)(async (param)=>{
|
|
152
|
+
await this.keyboardPress(param.keyName);
|
|
153
|
+
}),
|
|
154
|
+
(0, device_namespaceObject.defineAction)({
|
|
155
|
+
name: 'AndroidBackButton',
|
|
156
|
+
description: 'Trigger the system "back" operation on Android devices',
|
|
157
|
+
paramSchema: core_namespaceObject.z.object({}),
|
|
158
|
+
call: async ()=>{
|
|
159
|
+
await this.back();
|
|
160
|
+
}
|
|
161
|
+
}),
|
|
162
|
+
(0, device_namespaceObject.defineAction)({
|
|
163
|
+
name: 'AndroidHomeButton',
|
|
164
|
+
description: 'Trigger the system "home" operation on Android devices',
|
|
165
|
+
paramSchema: core_namespaceObject.z.object({}),
|
|
166
|
+
call: async ()=>{
|
|
167
|
+
await this.home();
|
|
168
|
+
}
|
|
169
|
+
}),
|
|
170
|
+
(0, device_namespaceObject.defineAction)({
|
|
171
|
+
name: 'AndroidRecentAppsButton',
|
|
172
|
+
description: 'Trigger the system "recent apps" operation on Android devices',
|
|
173
|
+
paramSchema: core_namespaceObject.z.object({}),
|
|
174
|
+
call: async ()=>{
|
|
175
|
+
await this.recentApps();
|
|
176
|
+
}
|
|
177
|
+
}),
|
|
178
|
+
(0, device_namespaceObject.defineAction)({
|
|
179
|
+
name: 'AndroidLongPress',
|
|
180
|
+
description: 'Trigger a long press on the screen at specified coordinates on Android devices',
|
|
181
|
+
paramSchema: core_namespaceObject.z.object({
|
|
182
|
+
duration: core_namespaceObject.z.number().optional().describe('The duration of the long press in milliseconds'),
|
|
183
|
+
locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The element to be long pressed')
|
|
184
|
+
}),
|
|
185
|
+
call: async (param)=>{
|
|
186
|
+
const element = param.locate;
|
|
187
|
+
if (!element) throw new Error('AndroidLongPress requires an element to be located');
|
|
188
|
+
const [x, y] = element.center;
|
|
189
|
+
await this.longPress(x, y, null == param ? void 0 : param.duration);
|
|
190
|
+
}
|
|
191
|
+
}),
|
|
192
|
+
(0, device_namespaceObject.defineAction)({
|
|
193
|
+
name: 'AndroidPull',
|
|
194
|
+
description: 'Trigger pull down to refresh or pull up actions',
|
|
195
|
+
paramSchema: core_namespaceObject.z.object({
|
|
196
|
+
direction: core_namespaceObject.z["enum"]([
|
|
197
|
+
'up',
|
|
198
|
+
'down'
|
|
199
|
+
]).describe('The direction to pull'),
|
|
200
|
+
distance: core_namespaceObject.z.number().optional().describe('The distance to pull (in pixels)'),
|
|
201
|
+
duration: core_namespaceObject.z.number().optional().describe('The duration of the pull (in milliseconds)'),
|
|
202
|
+
locate: (0, core_namespaceObject.getMidsceneLocationSchema)().optional().describe('The element to start the pull from (optional)')
|
|
203
|
+
}),
|
|
204
|
+
call: async (param)=>{
|
|
205
|
+
const element = param.locate;
|
|
206
|
+
const startPoint = element ? {
|
|
207
|
+
left: element.center[0],
|
|
208
|
+
top: element.center[1]
|
|
209
|
+
} : void 0;
|
|
210
|
+
if (!param || !param.direction) throw new Error('AndroidPull requires a direction parameter');
|
|
211
|
+
if ('down' === param.direction) await this.pullDown(startPoint, param.distance, param.duration);
|
|
212
|
+
else if ('up' === param.direction) await this.pullUp(startPoint, param.distance, param.duration);
|
|
213
|
+
else throw new Error(`Unknown pull direction: ${param.direction}`);
|
|
214
|
+
}
|
|
215
|
+
}),
|
|
216
|
+
(0, device_namespaceObject.defineActionClearInput)(async (param)=>{
|
|
217
|
+
const element = param.locate;
|
|
218
|
+
external_node_assert_default()(element, 'Element not found, cannot clear input');
|
|
219
|
+
await this.clearInput(element);
|
|
220
|
+
})
|
|
221
|
+
];
|
|
222
|
+
const customActions = this.customActions || [];
|
|
223
|
+
return [
|
|
224
|
+
...defaultActions,
|
|
225
|
+
...customActions
|
|
226
|
+
];
|
|
227
|
+
}
|
|
228
|
+
describe() {
|
|
229
|
+
return this.description || `DeviceId: ${this.deviceId}`;
|
|
230
|
+
}
|
|
231
|
+
async connect() {
|
|
232
|
+
return this.getAdb();
|
|
233
|
+
}
|
|
234
|
+
async getAdb() {
|
|
235
|
+
if (this.destroyed) throw new Error(`AndroidDevice ${this.deviceId} has been destroyed and cannot execute ADB commands`);
|
|
236
|
+
if (this.adb) return this.createAdbProxy(this.adb);
|
|
237
|
+
if (this.connectingAdb) return this.connectingAdb.then((adb)=>this.createAdbProxy(adb));
|
|
238
|
+
this.connectingAdb = (async ()=>{
|
|
239
|
+
let error = null;
|
|
240
|
+
debugDevice(`Initializing ADB with device ID: ${this.deviceId}`);
|
|
241
|
+
try {
|
|
242
|
+
var _this_options, _this_options1, _this_options2;
|
|
243
|
+
const androidAdbPath = (null == (_this_options = this.options) ? void 0 : _this_options.androidAdbPath) || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.SQAI_ADB_PATH);
|
|
244
|
+
const remoteAdbHost = (null == (_this_options1 = this.options) ? void 0 : _this_options1.remoteAdbHost) || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.SQAI_ADB_REMOTE_HOST);
|
|
245
|
+
const remoteAdbPort = (null == (_this_options2 = this.options) ? void 0 : _this_options2.remoteAdbPort) || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.SQAI_ADB_REMOTE_PORT);
|
|
246
|
+
this.adb = await new external_appium_adb_namespaceObject.ADB({
|
|
247
|
+
udid: this.deviceId,
|
|
248
|
+
adbExecTimeout: 60000,
|
|
249
|
+
executable: androidAdbPath ? {
|
|
250
|
+
path: androidAdbPath,
|
|
251
|
+
defaultArgs: []
|
|
252
|
+
} : void 0,
|
|
253
|
+
remoteAdbHost: remoteAdbHost || void 0,
|
|
254
|
+
remoteAdbPort: remoteAdbPort ? Number(remoteAdbPort) : void 0
|
|
255
|
+
});
|
|
256
|
+
const size = await this.getScreenSize();
|
|
257
|
+
this.description = `
|
|
258
|
+
DeviceId: ${this.deviceId}
|
|
259
|
+
ScreenSize:
|
|
260
|
+
${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[key]}${'override' === key && size[key] ? " \u2705" : ''}`).join('\n')}
|
|
261
|
+
`;
|
|
262
|
+
debugDevice('ADB initialized successfully', this.description);
|
|
263
|
+
return this.adb;
|
|
264
|
+
} catch (e) {
|
|
265
|
+
debugDevice(`Failed to initialize ADB: ${e}`);
|
|
266
|
+
error = new Error(`Unable to connect to device ${this.deviceId}: ${e}`);
|
|
267
|
+
} finally{
|
|
268
|
+
this.connectingAdb = null;
|
|
269
|
+
}
|
|
270
|
+
if (error) throw error;
|
|
271
|
+
throw new Error('ADB initialization failed unexpectedly');
|
|
272
|
+
})();
|
|
273
|
+
return this.connectingAdb;
|
|
274
|
+
}
|
|
275
|
+
createAdbProxy(adb) {
|
|
276
|
+
return new Proxy(adb, {
|
|
277
|
+
get: (target, prop)=>{
|
|
278
|
+
const originalMethod = target[prop];
|
|
279
|
+
if ('function' != typeof originalMethod) return originalMethod;
|
|
280
|
+
return async (...args)=>{
|
|
281
|
+
try {
|
|
282
|
+
debugDevice(`adb ${String(prop)} ${args.join(' ')}`);
|
|
283
|
+
const result = await originalMethod.apply(target, args);
|
|
284
|
+
debugDevice(`adb ${String(prop)} ${args.join(' ')} end`);
|
|
285
|
+
return result;
|
|
286
|
+
} catch (error) {
|
|
287
|
+
const methodName = String(prop);
|
|
288
|
+
const deviceId = this.deviceId;
|
|
289
|
+
debugDevice(`ADB error with device ${deviceId} when calling ${methodName}: ${error}`);
|
|
290
|
+
throw new Error(`ADB error with device ${deviceId} when calling ${methodName}, please check https://midscenejs.com/integrate-with-android.html#faq : ${error.message}`, {
|
|
291
|
+
cause: error
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
async launch(uri) {
|
|
299
|
+
const adb = await this.getAdb();
|
|
300
|
+
this.uri = uri;
|
|
301
|
+
try {
|
|
302
|
+
debugDevice(`Launching app: ${uri}`);
|
|
303
|
+
if (uri.startsWith('http://') || uri.startsWith('https://') || uri.includes('://')) await adb.startUri(uri);
|
|
304
|
+
else if (uri.includes('/')) {
|
|
305
|
+
const [appPackage, appActivity] = uri.split('/');
|
|
306
|
+
await adb.startApp({
|
|
307
|
+
pkg: appPackage,
|
|
308
|
+
activity: appActivity
|
|
309
|
+
});
|
|
310
|
+
} else await adb.activateApp(uri);
|
|
311
|
+
debugDevice(`Successfully launched: ${uri}`);
|
|
312
|
+
} catch (error) {
|
|
313
|
+
debugDevice(`Error launching ${uri}: ${error}`);
|
|
314
|
+
throw new Error(`Failed to launch ${uri}: ${error.message}`, {
|
|
315
|
+
cause: error
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
return this;
|
|
319
|
+
}
|
|
320
|
+
async execYadb(keyboardContent) {
|
|
321
|
+
await this.ensureYadb();
|
|
322
|
+
const adb = await this.getAdb();
|
|
323
|
+
await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboard "${keyboardContent}"`);
|
|
324
|
+
}
|
|
325
|
+
async getElementsInfo() {
|
|
326
|
+
return [];
|
|
327
|
+
}
|
|
328
|
+
async getElementsNodeTree() {
|
|
329
|
+
return {
|
|
330
|
+
node: null,
|
|
331
|
+
children: []
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
async getScreenSize() {
|
|
335
|
+
var _this_options, _this_options1;
|
|
336
|
+
const shouldCache = !((null == (_this_options = this.options) ? void 0 : _this_options.alwaysRefreshScreenInfo) ?? false);
|
|
337
|
+
if (shouldCache && this.cachedScreenSize) return this.cachedScreenSize;
|
|
338
|
+
const adb = await this.getAdb();
|
|
339
|
+
if ('number' == typeof (null == (_this_options1 = this.options) ? void 0 : _this_options1.displayId)) try {
|
|
340
|
+
var _this_options2;
|
|
341
|
+
const stdout = await adb.shell('dumpsys display');
|
|
342
|
+
if (null == (_this_options2 = this.options) ? void 0 : _this_options2.usePhysicalDisplayIdForDisplayLookup) {
|
|
343
|
+
const physicalDisplayId = await this.getPhysicalDisplayId();
|
|
344
|
+
if (physicalDisplayId) {
|
|
345
|
+
const lineRegex = new RegExp(`^.*uniqueId \"local:${physicalDisplayId}\".*$
|
|
346
|
+
`, 'm');
|
|
347
|
+
const lineMatch = stdout.match(lineRegex);
|
|
348
|
+
if (lineMatch) {
|
|
349
|
+
const targetLine = lineMatch[0];
|
|
350
|
+
const realMatch = targetLine.match(/real (\d+) x (\d+)/);
|
|
351
|
+
const rotationMatch = targetLine.match(/rotation (\d+)/);
|
|
352
|
+
if (realMatch && rotationMatch) {
|
|
353
|
+
const width = Number(realMatch[1]);
|
|
354
|
+
const height = Number(realMatch[2]);
|
|
355
|
+
const rotation = Number(rotationMatch[1]);
|
|
356
|
+
const sizeStr = `${width}x${height}`;
|
|
357
|
+
debugDevice(`Using display info for long ID ${physicalDisplayId}: ${sizeStr}, rotation: ${rotation}`);
|
|
358
|
+
const result = {
|
|
359
|
+
override: sizeStr,
|
|
360
|
+
physical: sizeStr,
|
|
361
|
+
orientation: rotation
|
|
362
|
+
};
|
|
363
|
+
if (shouldCache) this.cachedScreenSize = result;
|
|
364
|
+
return result;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
const viewportRegex = new RegExp(`DisplayViewport{[^}]*displayId=${this.options.displayId}[^}]*}`, 'g');
|
|
370
|
+
const match = stdout.match(viewportRegex);
|
|
371
|
+
if (match) {
|
|
372
|
+
const targetLine = match[0];
|
|
373
|
+
const physicalFrameMatch = targetLine.match(/physicalFrame=Rect\(\d+, \d+ - (\d+), (\d+)\)/);
|
|
374
|
+
const orientationMatch = targetLine.match(/orientation=(\d+)/);
|
|
375
|
+
if (physicalFrameMatch && orientationMatch) {
|
|
376
|
+
const width = Number(physicalFrameMatch[1]);
|
|
377
|
+
const height = Number(physicalFrameMatch[2]);
|
|
378
|
+
const rotation = Number(orientationMatch[1]);
|
|
379
|
+
const sizeStr = `${width}x${height}`;
|
|
380
|
+
debugDevice(`Using display info for display ID ${this.options.displayId}: ${sizeStr}, rotation: ${rotation}`);
|
|
381
|
+
const result = {
|
|
382
|
+
override: sizeStr,
|
|
383
|
+
physical: sizeStr,
|
|
384
|
+
orientation: rotation
|
|
385
|
+
};
|
|
386
|
+
if (shouldCache) this.cachedScreenSize = result;
|
|
387
|
+
return result;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
debugDevice(`Could not find display info for displayId ${this.options.displayId}`);
|
|
392
|
+
} catch (e) {
|
|
393
|
+
debugDevice(`Failed to get size from display info for display ${this.options.displayId}: ${e}`);
|
|
394
|
+
}
|
|
395
|
+
const stdout = await adb.shell([
|
|
396
|
+
'wm',
|
|
397
|
+
'size'
|
|
398
|
+
]);
|
|
399
|
+
const size = {
|
|
400
|
+
override: '',
|
|
401
|
+
physical: ''
|
|
402
|
+
};
|
|
403
|
+
const overrideSize = new RegExp(/Override size: ([^\r?\n]+)*/g).exec(stdout);
|
|
404
|
+
if (overrideSize && overrideSize.length >= 2 && overrideSize[1]) {
|
|
405
|
+
debugDevice(`Using Override size: ${overrideSize[1].trim()}`);
|
|
406
|
+
size.override = overrideSize[1].trim();
|
|
407
|
+
}
|
|
408
|
+
const physicalSize = new RegExp(/Physical size: ([^\r?\n]+)*/g).exec(stdout);
|
|
409
|
+
if (physicalSize && physicalSize.length >= 2) {
|
|
410
|
+
debugDevice(`Using Physical size: ${physicalSize[1].trim()}`);
|
|
411
|
+
size.physical = physicalSize[1].trim();
|
|
412
|
+
}
|
|
413
|
+
const orientation = await this.getDisplayOrientation();
|
|
414
|
+
if (size.override || size.physical) {
|
|
415
|
+
const result = {
|
|
416
|
+
...size,
|
|
417
|
+
orientation
|
|
418
|
+
};
|
|
419
|
+
if (shouldCache) this.cachedScreenSize = result;
|
|
420
|
+
return result;
|
|
421
|
+
}
|
|
422
|
+
throw new Error(`Failed to get screen size, output: ${stdout}`);
|
|
423
|
+
}
|
|
424
|
+
async initializeDevicePixelRatio() {
|
|
425
|
+
if (this.devicePixelRatioInitialized) return;
|
|
426
|
+
const densityNum = await this.getDisplayDensity();
|
|
427
|
+
this.devicePixelRatio = Number(densityNum) / 160;
|
|
428
|
+
debugDevice(`Initialized device pixel ratio: ${this.devicePixelRatio}`);
|
|
429
|
+
this.devicePixelRatioInitialized = true;
|
|
430
|
+
}
|
|
431
|
+
async getDisplayDensity() {
|
|
432
|
+
var _this_options;
|
|
433
|
+
const adb = await this.getAdb();
|
|
434
|
+
if ('number' == typeof (null == (_this_options = this.options) ? void 0 : _this_options.displayId)) try {
|
|
435
|
+
var _this_options1;
|
|
436
|
+
const stdout = await adb.shell('dumpsys display');
|
|
437
|
+
if (null == (_this_options1 = this.options) ? void 0 : _this_options1.usePhysicalDisplayIdForDisplayLookup) {
|
|
438
|
+
const physicalDisplayId = await this.getPhysicalDisplayId();
|
|
439
|
+
if (physicalDisplayId) {
|
|
440
|
+
const lineRegex = new RegExp(`^.*uniqueId \"local:${physicalDisplayId}\".*$
|
|
441
|
+
`, 'm');
|
|
442
|
+
const lineMatch = stdout.match(lineRegex);
|
|
443
|
+
if (lineMatch) {
|
|
444
|
+
const targetLine = lineMatch[0];
|
|
445
|
+
const densityMatch = targetLine.match(/density (\d+)/);
|
|
446
|
+
if (densityMatch) {
|
|
447
|
+
const density = Number(densityMatch[1]);
|
|
448
|
+
debugDevice(`Using display density for physical ID ${physicalDisplayId}: ${density}`);
|
|
449
|
+
return density;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
const displayDeviceRegex = new RegExp(`DisplayDevice:[\\s\\S]*?mDisplayId=${this.options.displayId}[\\s\\S]*?DisplayInfo{[^}]*density (\\d+)`, 'm');
|
|
455
|
+
const deviceBlockMatch = stdout.match(displayDeviceRegex);
|
|
456
|
+
if (deviceBlockMatch) {
|
|
457
|
+
const density = Number(deviceBlockMatch[1]);
|
|
458
|
+
debugDevice(`Using display density for display ID ${this.options.displayId}: ${density}`);
|
|
459
|
+
return density;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
} catch (e) {
|
|
463
|
+
debugDevice(`Failed to get density from display info: ${e}`);
|
|
464
|
+
}
|
|
465
|
+
const density = await adb.getScreenDensity();
|
|
466
|
+
return density ?? 160;
|
|
467
|
+
}
|
|
468
|
+
async getDisplayOrientation() {
|
|
469
|
+
var _this_options;
|
|
470
|
+
const shouldCache = !((null == (_this_options = this.options) ? void 0 : _this_options.alwaysRefreshScreenInfo) ?? false);
|
|
471
|
+
if (shouldCache && null !== this.cachedOrientation) return this.cachedOrientation;
|
|
472
|
+
const adb = await this.getAdb();
|
|
473
|
+
let orientation = 0;
|
|
474
|
+
try {
|
|
475
|
+
const orientationStdout = await adb.shell(`dumpsys${this.getDisplayArg()} input | grep SurfaceOrientation`);
|
|
476
|
+
const orientationMatch = orientationStdout.match(/SurfaceOrientation:\s*(\d)/);
|
|
477
|
+
if (!orientationMatch) throw new Error('Failed to get orientation from input');
|
|
478
|
+
orientation = Number(orientationMatch[1]);
|
|
479
|
+
debugDevice(`Screen orientation: ${orientation}`);
|
|
480
|
+
} catch (e) {
|
|
481
|
+
debugDevice('Failed to get orientation from input, try display');
|
|
482
|
+
try {
|
|
483
|
+
const orientationStdout = await adb.shell(`dumpsys${this.getDisplayArg()} display | grep mCurrentOrientation`);
|
|
484
|
+
const orientationMatch = orientationStdout.match(/mCurrentOrientation=(\d)/);
|
|
485
|
+
if (!orientationMatch) throw new Error('Failed to get orientation from display');
|
|
486
|
+
orientation = Number(orientationMatch[1]);
|
|
487
|
+
debugDevice(`Screen orientation (fallback): ${orientation}`);
|
|
488
|
+
} catch (e2) {
|
|
489
|
+
orientation = 0;
|
|
490
|
+
debugDevice('Failed to get orientation from display, default to 0');
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
if (shouldCache) this.cachedOrientation = orientation;
|
|
494
|
+
return orientation;
|
|
495
|
+
}
|
|
496
|
+
async size() {
|
|
497
|
+
var _this_options;
|
|
498
|
+
await this.initializeDevicePixelRatio();
|
|
499
|
+
const screenSize = await this.getScreenSize();
|
|
500
|
+
const match = (screenSize.override || screenSize.physical).match(/(\d+)x(\d+)/);
|
|
501
|
+
if (!match || match.length < 3) throw new Error(`Unable to parse screen size: ${screenSize}`);
|
|
502
|
+
const isLandscape = 1 === screenSize.orientation || 3 === screenSize.orientation;
|
|
503
|
+
const width = Number.parseInt(match[isLandscape ? 2 : 1], 10);
|
|
504
|
+
const height = Number.parseInt(match[isLandscape ? 1 : 2], 10);
|
|
505
|
+
const scale = (null == (_this_options = this.options) ? void 0 : _this_options.screenshotResizeScale) ?? 1 / this.devicePixelRatio;
|
|
506
|
+
this.scalingRatio = scale;
|
|
507
|
+
const logicalWidth = Math.round(width * scale);
|
|
508
|
+
const logicalHeight = Math.round(height * scale);
|
|
509
|
+
return {
|
|
510
|
+
width: logicalWidth,
|
|
511
|
+
height: logicalHeight,
|
|
512
|
+
dpr: this.devicePixelRatio
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
adjustCoordinates(x, y) {
|
|
516
|
+
const scale = this.scalingRatio;
|
|
517
|
+
return {
|
|
518
|
+
x: Math.round(x / scale),
|
|
519
|
+
y: Math.round(y / scale)
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
calculateScrollEndPoint(start, deltaX, deltaY, maxWidth, maxHeight) {
|
|
523
|
+
const minScrollDistance = 50;
|
|
524
|
+
let actualScrollDistanceX = 0;
|
|
525
|
+
let actualScrollDistanceY = 0;
|
|
526
|
+
if (0 !== deltaX) {
|
|
527
|
+
const maxAvailableX = deltaX > 0 ? maxWidth - start.x : start.x;
|
|
528
|
+
actualScrollDistanceX = Math.min(Math.abs(deltaX), maxAvailableX);
|
|
529
|
+
const minScrollX = Math.min(minScrollDistance, actualScrollDistanceX);
|
|
530
|
+
actualScrollDistanceX = Math.max(minScrollX, actualScrollDistanceX);
|
|
531
|
+
}
|
|
532
|
+
if (0 !== deltaY) {
|
|
533
|
+
const maxAvailableY = deltaY > 0 ? maxHeight - start.y : start.y;
|
|
534
|
+
actualScrollDistanceY = Math.min(Math.abs(deltaY), maxAvailableY);
|
|
535
|
+
const minScrollY = Math.min(minScrollDistance, actualScrollDistanceY);
|
|
536
|
+
actualScrollDistanceY = Math.max(minScrollY, actualScrollDistanceY);
|
|
537
|
+
}
|
|
538
|
+
const endX = Math.round(0 === deltaX ? start.x : deltaX > 0 ? Math.min(maxWidth, start.x + actualScrollDistanceX) : Math.max(0, start.x - actualScrollDistanceX));
|
|
539
|
+
const endY = Math.round(0 === deltaY ? start.y : deltaY > 0 ? Math.min(maxHeight, start.y + actualScrollDistanceY) : Math.max(0, start.y - actualScrollDistanceY));
|
|
540
|
+
return {
|
|
541
|
+
x: endX,
|
|
542
|
+
y: endY
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
async screenshotBase64() {
|
|
546
|
+
var _this_options;
|
|
547
|
+
debugDevice('screenshotBase64 begin');
|
|
548
|
+
const adb = await this.getAdb();
|
|
549
|
+
let screenshotBuffer;
|
|
550
|
+
const androidScreenshotPath = `/data/local/tmp/midscene_screenshot_${(0, shared_utils_namespaceObject.uuid)()}.png`;
|
|
551
|
+
const useShellScreencap = 'number' == typeof (null == (_this_options = this.options) ? void 0 : _this_options.displayId);
|
|
552
|
+
try {
|
|
553
|
+
if (useShellScreencap) {
|
|
554
|
+
var _this_options1;
|
|
555
|
+
throw new Error(`Display ${null == (_this_options1 = this.options) ? void 0 : _this_options1.displayId} requires shell screencap`);
|
|
556
|
+
}
|
|
557
|
+
debugDevice('Taking screenshot via adb.takeScreenshot');
|
|
558
|
+
screenshotBuffer = await adb.takeScreenshot(null);
|
|
559
|
+
debugDevice('adb.takeScreenshot completed');
|
|
560
|
+
if (!screenshotBuffer) throw new Error('Failed to capture screenshot: screenshotBuffer is null');
|
|
561
|
+
if (!(0, img_namespaceObject.isValidPNGImageBuffer)(screenshotBuffer)) {
|
|
562
|
+
debugDevice('Invalid image buffer detected: not a valid image format');
|
|
563
|
+
throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
|
|
564
|
+
}
|
|
565
|
+
} catch (error) {
|
|
566
|
+
debugDevice(`Taking screenshot via adb.takeScreenshot failed or was skipped: ${error}`);
|
|
567
|
+
const screenshotPath = (0, utils_namespaceObject.getTmpFile)('png');
|
|
568
|
+
try {
|
|
569
|
+
var _this_options2, _this_options3;
|
|
570
|
+
debugDevice('Fallback: taking screenshot via shell screencap');
|
|
571
|
+
const displayId = (null == (_this_options2 = this.options) ? void 0 : _this_options2.usePhysicalDisplayIdForScreenshot) ? await this.getPhysicalDisplayId() : null == (_this_options3 = this.options) ? void 0 : _this_options3.displayId;
|
|
572
|
+
const displayArg = displayId ? `-d ${displayId}` : '';
|
|
573
|
+
try {
|
|
574
|
+
await adb.shell(`screencap -p ${displayArg} ${androidScreenshotPath}`.trim());
|
|
575
|
+
debugDevice('adb.shell screencap completed');
|
|
576
|
+
} catch (screencapError) {
|
|
577
|
+
debugDevice('screencap failed, using forceScreenshot');
|
|
578
|
+
await this.forceScreenshot(androidScreenshotPath);
|
|
579
|
+
debugDevice('forceScreenshot completed');
|
|
580
|
+
}
|
|
581
|
+
debugDevice('Pulling screenshot file from device');
|
|
582
|
+
await adb.pull(androidScreenshotPath, screenshotPath);
|
|
583
|
+
debugDevice(`adb.pull completed, local path: ${screenshotPath}`);
|
|
584
|
+
screenshotBuffer = await external_node_fs_default().promises.readFile(screenshotPath);
|
|
585
|
+
} finally{
|
|
586
|
+
await adb.shell(`rm ${androidScreenshotPath}`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
debugDevice('Converting to base64');
|
|
590
|
+
const result = (0, img_namespaceObject.createImgBase64ByFormat)('png', screenshotBuffer.toString('base64'));
|
|
591
|
+
debugDevice('screenshotBase64 end');
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
async clearInput(element) {
|
|
595
|
+
var _this_options;
|
|
596
|
+
if (!element) return;
|
|
597
|
+
await this.ensureYadb();
|
|
598
|
+
const adb = await this.getAdb();
|
|
599
|
+
await this.mouseClick(element.center[0], element.center[1]);
|
|
600
|
+
const IME_STRATEGY = ((null == (_this_options = this.options) ? void 0 : _this_options.imeStrategy) || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.SQAI_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
601
|
+
if (IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII) await adb.clearTextField(100);
|
|
602
|
+
else await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboard "~CLEAR~"`);
|
|
603
|
+
if (await adb.isSoftKeyboardPresent()) return;
|
|
604
|
+
await this.mouseClick(element.center[0], element.center[1]);
|
|
605
|
+
}
|
|
606
|
+
async forceScreenshot(path) {
|
|
607
|
+
await this.ensureYadb();
|
|
608
|
+
const adb = await this.getAdb();
|
|
609
|
+
await adb.shell(`app_process -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -screenshot ${path}`);
|
|
610
|
+
}
|
|
611
|
+
async url() {
|
|
612
|
+
return '';
|
|
613
|
+
}
|
|
614
|
+
async scrollUntilTop(startPoint) {
|
|
615
|
+
if (startPoint) {
|
|
616
|
+
const { height } = await this.size();
|
|
617
|
+
const start = {
|
|
618
|
+
x: Math.round(startPoint.left),
|
|
619
|
+
y: Math.round(startPoint.top)
|
|
620
|
+
};
|
|
621
|
+
const end = {
|
|
622
|
+
x: start.x,
|
|
623
|
+
y: Math.round(height)
|
|
624
|
+
};
|
|
625
|
+
await (0, shared_utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.mouseDrag(start, end, defaultFastScrollDuration));
|
|
626
|
+
await (0, utils_namespaceObject.sleep)(1000);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
await (0, shared_utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.scroll(0, -9999999, defaultFastScrollDuration));
|
|
630
|
+
await (0, utils_namespaceObject.sleep)(1000);
|
|
631
|
+
}
|
|
632
|
+
async scrollUntilBottom(startPoint) {
|
|
633
|
+
if (startPoint) {
|
|
634
|
+
const start = {
|
|
635
|
+
x: Math.round(startPoint.left),
|
|
636
|
+
y: Math.round(startPoint.top)
|
|
637
|
+
};
|
|
638
|
+
const end = {
|
|
639
|
+
x: start.x,
|
|
640
|
+
y: 0
|
|
641
|
+
};
|
|
642
|
+
await (0, shared_utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.mouseDrag(start, end, defaultFastScrollDuration));
|
|
643
|
+
await (0, utils_namespaceObject.sleep)(1000);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
await (0, shared_utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.scroll(0, 9999999, defaultFastScrollDuration));
|
|
647
|
+
await (0, utils_namespaceObject.sleep)(1000);
|
|
648
|
+
}
|
|
649
|
+
async scrollUntilLeft(startPoint) {
|
|
650
|
+
if (startPoint) {
|
|
651
|
+
const { width } = await this.size();
|
|
652
|
+
const start = {
|
|
653
|
+
x: Math.round(startPoint.left),
|
|
654
|
+
y: Math.round(startPoint.top)
|
|
655
|
+
};
|
|
656
|
+
const end = {
|
|
657
|
+
x: Math.round(width),
|
|
658
|
+
y: start.y
|
|
659
|
+
};
|
|
660
|
+
await (0, shared_utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.mouseDrag(start, end, defaultFastScrollDuration));
|
|
661
|
+
await (0, utils_namespaceObject.sleep)(1000);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
await (0, shared_utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.scroll(-9999999, 0, defaultFastScrollDuration));
|
|
665
|
+
await (0, utils_namespaceObject.sleep)(1000);
|
|
666
|
+
}
|
|
667
|
+
async scrollUntilRight(startPoint) {
|
|
668
|
+
if (startPoint) {
|
|
669
|
+
const start = {
|
|
670
|
+
x: Math.round(startPoint.left),
|
|
671
|
+
y: Math.round(startPoint.top)
|
|
672
|
+
};
|
|
673
|
+
const end = {
|
|
674
|
+
x: 0,
|
|
675
|
+
y: start.y
|
|
676
|
+
};
|
|
677
|
+
await (0, shared_utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.mouseDrag(start, end, defaultFastScrollDuration));
|
|
678
|
+
await (0, utils_namespaceObject.sleep)(1000);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
await (0, shared_utils_namespaceObject.repeat)(defaultScrollUntilTimes, ()=>this.scroll(9999999, 0, defaultFastScrollDuration));
|
|
682
|
+
await (0, utils_namespaceObject.sleep)(1000);
|
|
683
|
+
}
|
|
684
|
+
async scrollUp(distance, startPoint) {
|
|
685
|
+
const { height } = await this.size();
|
|
686
|
+
const scrollDistance = Math.round(distance || height);
|
|
687
|
+
if (startPoint) {
|
|
688
|
+
const start = {
|
|
689
|
+
x: Math.round(startPoint.left),
|
|
690
|
+
y: Math.round(startPoint.top)
|
|
691
|
+
};
|
|
692
|
+
const end = this.calculateScrollEndPoint(start, 0, scrollDistance, 0, height);
|
|
693
|
+
await this.mouseDrag(start, end);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
await this.scroll(0, -scrollDistance);
|
|
697
|
+
}
|
|
698
|
+
async scrollDown(distance, startPoint) {
|
|
699
|
+
const { height } = await this.size();
|
|
700
|
+
const scrollDistance = Math.round(distance || height);
|
|
701
|
+
if (startPoint) {
|
|
702
|
+
const start = {
|
|
703
|
+
x: Math.round(startPoint.left),
|
|
704
|
+
y: Math.round(startPoint.top)
|
|
705
|
+
};
|
|
706
|
+
const end = this.calculateScrollEndPoint(start, 0, -scrollDistance, 0, height);
|
|
707
|
+
await this.mouseDrag(start, end);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
await this.scroll(0, scrollDistance);
|
|
711
|
+
}
|
|
712
|
+
async scrollLeft(distance, startPoint) {
|
|
713
|
+
const { width } = await this.size();
|
|
714
|
+
const scrollDistance = Math.round(distance || width);
|
|
715
|
+
if (startPoint) {
|
|
716
|
+
const start = {
|
|
717
|
+
x: Math.round(startPoint.left),
|
|
718
|
+
y: Math.round(startPoint.top)
|
|
719
|
+
};
|
|
720
|
+
const end = this.calculateScrollEndPoint(start, scrollDistance, 0, width, 0);
|
|
721
|
+
await this.mouseDrag(start, end);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
await this.scroll(-scrollDistance, 0);
|
|
725
|
+
}
|
|
726
|
+
async scrollRight(distance, startPoint) {
|
|
727
|
+
const { width } = await this.size();
|
|
728
|
+
const scrollDistance = Math.round(distance || width);
|
|
729
|
+
if (startPoint) {
|
|
730
|
+
const start = {
|
|
731
|
+
x: Math.round(startPoint.left),
|
|
732
|
+
y: Math.round(startPoint.top)
|
|
733
|
+
};
|
|
734
|
+
const end = this.calculateScrollEndPoint(start, -scrollDistance, 0, width, 0);
|
|
735
|
+
await this.mouseDrag(start, end);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
await this.scroll(scrollDistance, 0);
|
|
739
|
+
}
|
|
740
|
+
async ensureYadb() {
|
|
741
|
+
if (!this.yadbPushed) {
|
|
742
|
+
const adb = await this.getAdb();
|
|
743
|
+
const androidPkgJson = (0, external_node_module_namespaceObject.createRequire)(__rslib_import_meta_url__).resolve('@sqaitech/android/package.json');
|
|
744
|
+
const yadbBin = external_node_path_default().join(external_node_path_default().dirname(androidPkgJson), 'bin', 'yadb');
|
|
745
|
+
await adb.push(yadbBin, '/data/local/tmp');
|
|
746
|
+
this.yadbPushed = true;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
async keyboardType(text, options) {
|
|
750
|
+
var _this_options, _this_options1;
|
|
751
|
+
if (!text) return;
|
|
752
|
+
const adb = await this.getAdb();
|
|
753
|
+
const isChinese = /[\p{Script=Han}\p{sc=Hani}]/u.test(text);
|
|
754
|
+
const IME_STRATEGY = ((null == (_this_options = this.options) ? void 0 : _this_options.imeStrategy) || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.SQAI_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
755
|
+
const shouldAutoDismissKeyboard = (null == options ? void 0 : options.autoDismissKeyboard) ?? (null == (_this_options1 = this.options) ? void 0 : _this_options1.autoDismissKeyboard) ?? true;
|
|
756
|
+
if (IME_STRATEGY === IME_STRATEGY_ALWAYS_YADB || IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII && isChinese) await this.execYadb(text);
|
|
757
|
+
else await adb.inputText(text);
|
|
758
|
+
if (true === shouldAutoDismissKeyboard) await this.hideKeyboard(options);
|
|
759
|
+
}
|
|
760
|
+
normalizeKeyName(key) {
|
|
761
|
+
const keyMap = {
|
|
762
|
+
enter: 'Enter',
|
|
763
|
+
backspace: 'Backspace',
|
|
764
|
+
tab: 'Tab',
|
|
765
|
+
escape: 'Escape',
|
|
766
|
+
esc: 'Escape',
|
|
767
|
+
home: 'Home',
|
|
768
|
+
end: 'End',
|
|
769
|
+
arrowup: 'ArrowUp',
|
|
770
|
+
arrowdown: 'ArrowDown',
|
|
771
|
+
arrowleft: 'ArrowLeft',
|
|
772
|
+
arrowright: 'ArrowRight',
|
|
773
|
+
up: 'ArrowUp',
|
|
774
|
+
down: 'ArrowDown',
|
|
775
|
+
left: 'ArrowLeft',
|
|
776
|
+
right: 'ArrowRight'
|
|
777
|
+
};
|
|
778
|
+
const lowerKey = key.toLowerCase();
|
|
779
|
+
return keyMap[lowerKey] || key;
|
|
780
|
+
}
|
|
781
|
+
async keyboardPress(key) {
|
|
782
|
+
const keyCodeMap = {
|
|
783
|
+
Enter: 66,
|
|
784
|
+
Backspace: 67,
|
|
785
|
+
Tab: 61,
|
|
786
|
+
ArrowUp: 19,
|
|
787
|
+
ArrowDown: 20,
|
|
788
|
+
ArrowLeft: 21,
|
|
789
|
+
ArrowRight: 22,
|
|
790
|
+
Escape: 111,
|
|
791
|
+
Home: 3,
|
|
792
|
+
End: 123
|
|
793
|
+
};
|
|
794
|
+
const adb = await this.getAdb();
|
|
795
|
+
const normalizedKey = this.normalizeKeyName(key);
|
|
796
|
+
const keyCode = keyCodeMap[normalizedKey];
|
|
797
|
+
if (void 0 !== keyCode) await adb.keyevent(keyCode);
|
|
798
|
+
else if (1 === key.length) {
|
|
799
|
+
const asciiCode = key.toUpperCase().charCodeAt(0);
|
|
800
|
+
if (asciiCode >= 65 && asciiCode <= 90) await adb.keyevent(asciiCode - 36);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
async mouseClick(x, y) {
|
|
804
|
+
const adb = await this.getAdb();
|
|
805
|
+
const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
|
|
806
|
+
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} 150`);
|
|
807
|
+
}
|
|
808
|
+
async mouseDoubleClick(x, y) {
|
|
809
|
+
const adb = await this.getAdb();
|
|
810
|
+
const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
|
|
811
|
+
const tapCommand = `input${this.getDisplayArg()} tap ${adjustedX} ${adjustedY}`;
|
|
812
|
+
await adb.shell(tapCommand);
|
|
813
|
+
await (0, utils_namespaceObject.sleep)(50);
|
|
814
|
+
await adb.shell(tapCommand);
|
|
815
|
+
}
|
|
816
|
+
async mouseMove() {
|
|
817
|
+
return Promise.resolve();
|
|
818
|
+
}
|
|
819
|
+
async mouseDrag(from, to, duration) {
|
|
820
|
+
const adb = await this.getAdb();
|
|
821
|
+
const { x: fromX, y: fromY } = this.adjustCoordinates(from.x, from.y);
|
|
822
|
+
const { x: toX, y: toY } = this.adjustCoordinates(to.x, to.y);
|
|
823
|
+
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
824
|
+
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
|
|
825
|
+
}
|
|
826
|
+
async scroll(deltaX, deltaY, duration) {
|
|
827
|
+
if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
|
|
828
|
+
const { width, height } = await this.size();
|
|
829
|
+
const n = 4;
|
|
830
|
+
const startX = Math.round(deltaX < 0 ? width / n * (n - 1) : width / n);
|
|
831
|
+
const startY = Math.round(deltaY < 0 ? height / n * (n - 1) : height / n);
|
|
832
|
+
const maxNegativeDeltaX = startX;
|
|
833
|
+
const maxPositiveDeltaX = Math.round(width / n * (n - 1));
|
|
834
|
+
const maxNegativeDeltaY = startY;
|
|
835
|
+
const maxPositiveDeltaY = Math.round(height / n * (n - 1));
|
|
836
|
+
deltaX = Math.max(-maxNegativeDeltaX, Math.min(deltaX, maxPositiveDeltaX));
|
|
837
|
+
deltaY = Math.max(-maxNegativeDeltaY, Math.min(deltaY, maxPositiveDeltaY));
|
|
838
|
+
const endX = Math.round(startX - deltaX);
|
|
839
|
+
const endY = Math.round(startY - deltaY);
|
|
840
|
+
const { x: adjustedStartX, y: adjustedStartY } = this.adjustCoordinates(startX, startY);
|
|
841
|
+
const { x: adjustedEndX, y: adjustedEndY } = this.adjustCoordinates(endX, endY);
|
|
842
|
+
const adb = await this.getAdb();
|
|
843
|
+
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
844
|
+
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedStartX} ${adjustedStartY} ${adjustedEndX} ${adjustedEndY} ${swipeDuration}`);
|
|
845
|
+
}
|
|
846
|
+
async destroy() {
|
|
847
|
+
if (this.destroyed) return;
|
|
848
|
+
this.destroyed = true;
|
|
849
|
+
try {
|
|
850
|
+
if (this.adb) this.adb = null;
|
|
851
|
+
} catch (error) {
|
|
852
|
+
console.error('Error during cleanup:', error);
|
|
853
|
+
}
|
|
854
|
+
this.connectingAdb = null;
|
|
855
|
+
this.yadbPushed = false;
|
|
856
|
+
}
|
|
857
|
+
async back() {
|
|
858
|
+
const adb = await this.getAdb();
|
|
859
|
+
await adb.shell(`input${this.getDisplayArg()} keyevent 4`);
|
|
860
|
+
}
|
|
861
|
+
async home() {
|
|
862
|
+
const adb = await this.getAdb();
|
|
863
|
+
await adb.shell(`input${this.getDisplayArg()} keyevent 3`);
|
|
864
|
+
}
|
|
865
|
+
async recentApps() {
|
|
866
|
+
const adb = await this.getAdb();
|
|
867
|
+
await adb.shell(`input${this.getDisplayArg()} keyevent 187`);
|
|
868
|
+
}
|
|
869
|
+
async longPress(x, y, duration = 1000) {
|
|
870
|
+
const adb = await this.getAdb();
|
|
871
|
+
const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
|
|
872
|
+
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} ${duration}`);
|
|
873
|
+
}
|
|
874
|
+
async pullDown(startPoint, distance, duration = 800) {
|
|
875
|
+
const { width, height } = await this.size();
|
|
876
|
+
const start = startPoint ? {
|
|
877
|
+
x: Math.round(startPoint.left),
|
|
878
|
+
y: Math.round(startPoint.top)
|
|
879
|
+
} : {
|
|
880
|
+
x: Math.round(width / 2),
|
|
881
|
+
y: Math.round(0.15 * height)
|
|
882
|
+
};
|
|
883
|
+
const pullDistance = Math.round(distance || 0.5 * height);
|
|
884
|
+
const end = {
|
|
885
|
+
x: start.x,
|
|
886
|
+
y: start.y + pullDistance
|
|
887
|
+
};
|
|
888
|
+
await this.pullDrag(start, end, duration);
|
|
889
|
+
await (0, utils_namespaceObject.sleep)(200);
|
|
890
|
+
}
|
|
891
|
+
async pullDrag(from, to, duration) {
|
|
892
|
+
const adb = await this.getAdb();
|
|
893
|
+
const { x: fromX, y: fromY } = this.adjustCoordinates(from.x, from.y);
|
|
894
|
+
const { x: toX, y: toY } = this.adjustCoordinates(to.x, to.y);
|
|
895
|
+
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
|
|
896
|
+
}
|
|
897
|
+
async pullUp(startPoint, distance, duration = 600) {
|
|
898
|
+
const { width, height } = await this.size();
|
|
899
|
+
const start = startPoint ? {
|
|
900
|
+
x: Math.round(startPoint.left),
|
|
901
|
+
y: Math.round(startPoint.top)
|
|
902
|
+
} : {
|
|
903
|
+
x: Math.round(width / 2),
|
|
904
|
+
y: Math.round(0.85 * height)
|
|
905
|
+
};
|
|
906
|
+
const pullDistance = Math.round(distance || 0.4 * height);
|
|
907
|
+
const end = {
|
|
908
|
+
x: start.x,
|
|
909
|
+
y: start.y - pullDistance
|
|
910
|
+
};
|
|
911
|
+
await this.pullDrag(start, end, duration);
|
|
912
|
+
await (0, utils_namespaceObject.sleep)(100);
|
|
913
|
+
}
|
|
914
|
+
getDisplayArg() {
|
|
915
|
+
var _this_options;
|
|
916
|
+
return 'number' == typeof (null == (_this_options = this.options) ? void 0 : _this_options.displayId) ? ` -d ${this.options.displayId}` : '';
|
|
917
|
+
}
|
|
918
|
+
async getPhysicalDisplayId() {
|
|
919
|
+
var _this_options;
|
|
920
|
+
if ('number' != typeof (null == (_this_options = this.options) ? void 0 : _this_options.displayId)) return null;
|
|
921
|
+
const adb = await this.getAdb();
|
|
922
|
+
try {
|
|
923
|
+
const stdout = await adb.shell(`dumpsys SurfaceFlinger --display-id ${this.options.displayId}`);
|
|
924
|
+
const regex = new RegExp(`Display (\\d+) \\(HWC display ${this.options.displayId}\\):`);
|
|
925
|
+
const displayMatch = stdout.match(regex);
|
|
926
|
+
if (null == displayMatch ? void 0 : displayMatch[1]) {
|
|
927
|
+
debugDevice(`Found physical display ID: ${displayMatch[1]} for display ID: ${this.options.displayId}`);
|
|
928
|
+
return displayMatch[1];
|
|
929
|
+
}
|
|
930
|
+
debugDevice(`Could not find physical display ID for display ID: ${this.options.displayId}`);
|
|
931
|
+
return null;
|
|
932
|
+
} catch (error) {
|
|
933
|
+
debugDevice(`Error getting physical display ID: ${error}`);
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
async hideKeyboard(options, timeoutMs = 1000) {
|
|
938
|
+
var _this_options;
|
|
939
|
+
const adb = await this.getAdb();
|
|
940
|
+
const keyboardDismissStrategy = (null == options ? void 0 : options.keyboardDismissStrategy) ?? (null == (_this_options = this.options) ? void 0 : _this_options.keyboardDismissStrategy) ?? 'esc-first';
|
|
941
|
+
const keyboardStatus = await adb.isSoftKeyboardPresent();
|
|
942
|
+
const isKeyboardShown = 'boolean' == typeof keyboardStatus ? keyboardStatus : null == keyboardStatus ? void 0 : keyboardStatus.isKeyboardShown;
|
|
943
|
+
if (!isKeyboardShown) {
|
|
944
|
+
debugDevice('Keyboard has no UI; no closing necessary');
|
|
945
|
+
return false;
|
|
946
|
+
}
|
|
947
|
+
const keyCodes = 'back-first' === keyboardDismissStrategy ? [
|
|
948
|
+
4,
|
|
949
|
+
111
|
|
950
|
+
] : [
|
|
951
|
+
111,
|
|
952
|
+
4
|
|
953
|
+
];
|
|
954
|
+
for (const keyCode of keyCodes){
|
|
955
|
+
await adb.keyevent(keyCode);
|
|
956
|
+
const startTime = Date.now();
|
|
957
|
+
const intervalMs = 100;
|
|
958
|
+
while(Date.now() - startTime < timeoutMs){
|
|
959
|
+
await (0, utils_namespaceObject.sleep)(intervalMs);
|
|
960
|
+
const currentStatus = await adb.isSoftKeyboardPresent();
|
|
961
|
+
const isStillShown = 'boolean' == typeof currentStatus ? currentStatus : null == currentStatus ? void 0 : currentStatus.isKeyboardShown;
|
|
962
|
+
if (!isStillShown) {
|
|
963
|
+
debugDevice(`Keyboard hidden successfully with keycode ${keyCode}`);
|
|
964
|
+
return true;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
debugDevice(`Keyboard still shown after keycode ${keyCode}, trying next key`);
|
|
968
|
+
}
|
|
969
|
+
console.warn('Warning: Failed to hide the software keyboard after trying both ESC and BACK keys');
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
972
|
+
constructor(deviceId, options){
|
|
973
|
+
_define_property(this, "deviceId", void 0);
|
|
974
|
+
_define_property(this, "yadbPushed", false);
|
|
975
|
+
_define_property(this, "devicePixelRatio", 1);
|
|
976
|
+
_define_property(this, "devicePixelRatioInitialized", false);
|
|
977
|
+
_define_property(this, "scalingRatio", 1);
|
|
978
|
+
_define_property(this, "adb", null);
|
|
979
|
+
_define_property(this, "connectingAdb", null);
|
|
980
|
+
_define_property(this, "destroyed", false);
|
|
981
|
+
_define_property(this, "description", void 0);
|
|
982
|
+
_define_property(this, "customActions", void 0);
|
|
983
|
+
_define_property(this, "cachedScreenSize", null);
|
|
984
|
+
_define_property(this, "cachedOrientation", null);
|
|
985
|
+
_define_property(this, "interfaceType", 'android');
|
|
986
|
+
_define_property(this, "uri", void 0);
|
|
987
|
+
_define_property(this, "options", void 0);
|
|
988
|
+
external_node_assert_default()(deviceId, 'deviceId is required for AndroidDevice');
|
|
989
|
+
this.deviceId = deviceId;
|
|
990
|
+
this.options = options;
|
|
991
|
+
this.customActions = null == options ? void 0 : options.customActions;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
const agent_namespaceObject = require("@sqaitech/core/agent");
|
|
995
|
+
const debugUtils = (0, logger_namespaceObject.getDebug)('android:utils');
|
|
996
|
+
async function getConnectedDevices() {
|
|
997
|
+
try {
|
|
998
|
+
const adb = await external_appium_adb_namespaceObject.ADB.createADB({
|
|
999
|
+
adbExecTimeout: 60000
|
|
1000
|
+
});
|
|
1001
|
+
const devices = await adb.getConnectedDevices();
|
|
1002
|
+
debugUtils(`Found ${devices.length} connected devices: `, devices);
|
|
1003
|
+
return devices;
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
console.error('Failed to get device list:', error);
|
|
1006
|
+
throw new Error(`Unable to get connected Android device list, please check https://midscenejs.com/integrate-with-android.html#faq : ${error.message}`, {
|
|
1007
|
+
cause: error
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
const debugAgent = (0, logger_namespaceObject.getDebug)('android:agent');
|
|
1012
|
+
class AndroidAgent extends agent_namespaceObject.Agent {
|
|
1013
|
+
async launch(uri) {
|
|
1014
|
+
const device = this.page;
|
|
1015
|
+
await device.launch(uri);
|
|
1016
|
+
}
|
|
1017
|
+
async runAdbShell(command) {
|
|
1018
|
+
const adb = await this.page.getAdb();
|
|
1019
|
+
return await adb.shell(command);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
async function agentFromAdbDevice(deviceId, opts) {
|
|
1023
|
+
if (!deviceId) {
|
|
1024
|
+
const devices = await getConnectedDevices();
|
|
1025
|
+
deviceId = devices[0].udid;
|
|
1026
|
+
debugAgent('deviceId not specified, will use the first device (id = %s)', deviceId);
|
|
1027
|
+
}
|
|
1028
|
+
const device = new AndroidDevice(deviceId, {
|
|
1029
|
+
autoDismissKeyboard: null == opts ? void 0 : opts.autoDismissKeyboard,
|
|
1030
|
+
androidAdbPath: null == opts ? void 0 : opts.androidAdbPath,
|
|
1031
|
+
remoteAdbHost: null == opts ? void 0 : opts.remoteAdbHost,
|
|
1032
|
+
remoteAdbPort: null == opts ? void 0 : opts.remoteAdbPort,
|
|
1033
|
+
imeStrategy: null == opts ? void 0 : opts.imeStrategy,
|
|
1034
|
+
displayId: null == opts ? void 0 : opts.displayId,
|
|
1035
|
+
usePhysicalDisplayIdForScreenshot: null == opts ? void 0 : opts.usePhysicalDisplayIdForScreenshot,
|
|
1036
|
+
usePhysicalDisplayIdForDisplayLookup: null == opts ? void 0 : opts.usePhysicalDisplayIdForDisplayLookup,
|
|
1037
|
+
screenshotResizeScale: null == opts ? void 0 : opts.screenshotResizeScale,
|
|
1038
|
+
alwaysRefreshScreenInfo: null == opts ? void 0 : opts.alwaysRefreshScreenInfo
|
|
1039
|
+
});
|
|
1040
|
+
await device.connect();
|
|
1041
|
+
return new AndroidAgent(device, opts);
|
|
1042
|
+
}
|
|
1043
|
+
exports.AndroidAgent = __webpack_exports__.AndroidAgent;
|
|
1044
|
+
exports.AndroidDevice = __webpack_exports__.AndroidDevice;
|
|
1045
|
+
exports.agentFromAdbDevice = __webpack_exports__.agentFromAdbDevice;
|
|
1046
|
+
exports.getConnectedDevices = __webpack_exports__.getConnectedDevices;
|
|
1047
|
+
exports.overrideAIConfig = __webpack_exports__.overrideAIConfig;
|
|
1048
|
+
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
1049
|
+
"AndroidAgent",
|
|
1050
|
+
"AndroidDevice",
|
|
1051
|
+
"agentFromAdbDevice",
|
|
1052
|
+
"getConnectedDevices",
|
|
1053
|
+
"overrideAIConfig"
|
|
1054
|
+
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
1055
|
+
Object.defineProperty(exports, '__esModule', {
|
|
1056
|
+
value: true
|
|
1057
|
+
});
|