@midscene/android 0.30.10 → 0.30.11-beta-20251218071621.0
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/dist/es/index.mjs +113 -101
- package/dist/lib/index.js +117 -105
- package/dist/types/index.d.ts +55 -24
- package/package.json +7 -6
package/dist/es/index.mjs
CHANGED
|
@@ -55,14 +55,13 @@ class AndroidDevice {
|
|
|
55
55
|
locate: getMidsceneLocationSchema().describe('The input field to be filled').optional()
|
|
56
56
|
}),
|
|
57
57
|
call: async (param)=>{
|
|
58
|
-
var _this_options;
|
|
59
58
|
const element = param.locate;
|
|
60
59
|
if (element) {
|
|
61
60
|
if ('append' !== param.mode) await this.clearInput(element);
|
|
62
61
|
}
|
|
63
62
|
if ('clear' === param.mode) return;
|
|
64
63
|
if (!param || !param.value) return;
|
|
65
|
-
const autoDismissKeyboard = param.autoDismissKeyboard ??
|
|
64
|
+
const autoDismissKeyboard = param.autoDismissKeyboard ?? this.options?.autoDismissKeyboard;
|
|
66
65
|
await this.keyboardType(param.value, {
|
|
67
66
|
autoDismissKeyboard
|
|
68
67
|
});
|
|
@@ -74,18 +73,18 @@ class AndroidDevice {
|
|
|
74
73
|
left: element.center[0],
|
|
75
74
|
top: element.center[1]
|
|
76
75
|
} : void 0;
|
|
77
|
-
const scrollToEventName =
|
|
78
|
-
if ('
|
|
79
|
-
else if ('
|
|
80
|
-
else if ('
|
|
81
|
-
else if ('
|
|
82
|
-
else if ('
|
|
76
|
+
const scrollToEventName = param?.scrollType;
|
|
77
|
+
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
78
|
+
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
79
|
+
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
80
|
+
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
81
|
+
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
83
82
|
else {
|
|
84
|
-
if (
|
|
83
|
+
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
|
|
85
84
|
else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
|
|
86
85
|
else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
|
|
87
86
|
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
88
|
-
else await this.scrollDown(
|
|
87
|
+
else await this.scrollDown(param?.distance || void 0, startingPoint);
|
|
89
88
|
await sleep(500);
|
|
90
89
|
}
|
|
91
90
|
}),
|
|
@@ -105,30 +104,6 @@ class AndroidDevice {
|
|
|
105
104
|
defineActionKeyboardPress(async (param)=>{
|
|
106
105
|
await this.keyboardPress(param.keyName);
|
|
107
106
|
}),
|
|
108
|
-
defineAction({
|
|
109
|
-
name: 'AndroidBackButton',
|
|
110
|
-
description: 'Trigger the system "back" operation on Android devices',
|
|
111
|
-
paramSchema: z.object({}),
|
|
112
|
-
call: async ()=>{
|
|
113
|
-
await this.back();
|
|
114
|
-
}
|
|
115
|
-
}),
|
|
116
|
-
defineAction({
|
|
117
|
-
name: 'AndroidHomeButton',
|
|
118
|
-
description: 'Trigger the system "home" operation on Android devices',
|
|
119
|
-
paramSchema: z.object({}),
|
|
120
|
-
call: async ()=>{
|
|
121
|
-
await this.home();
|
|
122
|
-
}
|
|
123
|
-
}),
|
|
124
|
-
defineAction({
|
|
125
|
-
name: 'AndroidRecentAppsButton',
|
|
126
|
-
description: 'Trigger the system "recent apps" operation on Android devices',
|
|
127
|
-
paramSchema: z.object({}),
|
|
128
|
-
call: async ()=>{
|
|
129
|
-
await this.recentApps();
|
|
130
|
-
}
|
|
131
|
-
}),
|
|
132
107
|
defineAction({
|
|
133
108
|
name: 'AndroidLongPress',
|
|
134
109
|
description: 'Trigger a long press on the screen at specified coordinates on Android devices',
|
|
@@ -140,7 +115,7 @@ class AndroidDevice {
|
|
|
140
115
|
const element = param.locate;
|
|
141
116
|
if (!element) throw new Error('AndroidLongPress requires an element to be located');
|
|
142
117
|
const [x, y] = element.center;
|
|
143
|
-
await this.longPress(x, y,
|
|
118
|
+
await this.longPress(x, y, param?.duration);
|
|
144
119
|
}
|
|
145
120
|
}),
|
|
146
121
|
defineAction({
|
|
@@ -173,9 +148,11 @@ class AndroidDevice {
|
|
|
173
148
|
await this.clearInput(element);
|
|
174
149
|
})
|
|
175
150
|
];
|
|
151
|
+
const platformSpecificActions = Object.values(createPlatformActions(this));
|
|
176
152
|
const customActions = this.customActions || [];
|
|
177
153
|
return [
|
|
178
154
|
...defaultActions,
|
|
155
|
+
...platformSpecificActions,
|
|
179
156
|
...customActions
|
|
180
157
|
];
|
|
181
158
|
}
|
|
@@ -193,10 +170,9 @@ class AndroidDevice {
|
|
|
193
170
|
let error = null;
|
|
194
171
|
debugDevice(`Initializing ADB with device ID: ${this.deviceId}`);
|
|
195
172
|
try {
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
const remoteAdbPort = (null == (_this_options2 = this.options) ? void 0 : _this_options2.remoteAdbPort) || globalConfigManager.getEnvConfigValue(MIDSCENE_ADB_REMOTE_PORT);
|
|
173
|
+
const androidAdbPath = this.options?.androidAdbPath || globalConfigManager.getEnvConfigValue(MIDSCENE_ADB_PATH);
|
|
174
|
+
const remoteAdbHost = this.options?.remoteAdbHost || globalConfigManager.getEnvConfigValue(MIDSCENE_ADB_REMOTE_HOST);
|
|
175
|
+
const remoteAdbPort = this.options?.remoteAdbPort || globalConfigManager.getEnvConfigValue(MIDSCENE_ADB_REMOTE_PORT);
|
|
200
176
|
this.adb = await new ADB({
|
|
201
177
|
udid: this.deviceId,
|
|
202
178
|
adbExecTimeout: 60000,
|
|
@@ -211,7 +187,7 @@ class AndroidDevice {
|
|
|
211
187
|
this.description = `
|
|
212
188
|
DeviceId: ${this.deviceId}
|
|
213
189
|
ScreenSize:
|
|
214
|
-
${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[key]}${'override' === key && size[key] ?
|
|
190
|
+
${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[key]}${'override' === key && size[key] ? ' ✅' : ''}`).join('\n')}
|
|
215
191
|
`;
|
|
216
192
|
debugDevice('ADB initialized successfully', this.description);
|
|
217
193
|
return this.adb;
|
|
@@ -286,14 +262,12 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
286
262
|
};
|
|
287
263
|
}
|
|
288
264
|
async getScreenSize() {
|
|
289
|
-
|
|
290
|
-
const shouldCache = !((null == (_this_options = this.options) ? void 0 : _this_options.alwaysRefreshScreenInfo) ?? false);
|
|
265
|
+
const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
|
|
291
266
|
if (shouldCache && this.cachedScreenSize) return this.cachedScreenSize;
|
|
292
267
|
const adb = await this.getAdb();
|
|
293
|
-
if ('number' == typeof
|
|
294
|
-
var _this_options2;
|
|
268
|
+
if ('number' == typeof this.options?.displayId) try {
|
|
295
269
|
const stdout = await adb.shell('dumpsys display');
|
|
296
|
-
if (
|
|
270
|
+
if (this.options?.usePhysicalDisplayIdForDisplayLookup) {
|
|
297
271
|
const physicalDisplayId = await this.getPhysicalDisplayId();
|
|
298
272
|
if (physicalDisplayId) {
|
|
299
273
|
const lineRegex = new RegExp(`^.*uniqueId \"local:${physicalDisplayId}\".*$
|
|
@@ -312,7 +286,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
312
286
|
const result = {
|
|
313
287
|
override: sizeStr,
|
|
314
288
|
physical: sizeStr,
|
|
315
|
-
orientation: rotation
|
|
289
|
+
orientation: rotation,
|
|
290
|
+
isCurrentOrientation: true
|
|
316
291
|
};
|
|
317
292
|
if (shouldCache) this.cachedScreenSize = result;
|
|
318
293
|
return result;
|
|
@@ -335,7 +310,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
335
310
|
const result = {
|
|
336
311
|
override: sizeStr,
|
|
337
312
|
physical: sizeStr,
|
|
338
|
-
orientation: rotation
|
|
313
|
+
orientation: rotation,
|
|
314
|
+
isCurrentOrientation: true
|
|
339
315
|
};
|
|
340
316
|
if (shouldCache) this.cachedScreenSize = result;
|
|
341
317
|
return result;
|
|
@@ -368,7 +344,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
368
344
|
if (size.override || size.physical) {
|
|
369
345
|
const result = {
|
|
370
346
|
...size,
|
|
371
|
-
orientation
|
|
347
|
+
orientation,
|
|
348
|
+
isCurrentOrientation: false
|
|
372
349
|
};
|
|
373
350
|
if (shouldCache) this.cachedScreenSize = result;
|
|
374
351
|
return result;
|
|
@@ -383,12 +360,10 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
383
360
|
this.devicePixelRatioInitialized = true;
|
|
384
361
|
}
|
|
385
362
|
async getDisplayDensity() {
|
|
386
|
-
var _this_options;
|
|
387
363
|
const adb = await this.getAdb();
|
|
388
|
-
if ('number' == typeof
|
|
389
|
-
var _this_options1;
|
|
364
|
+
if ('number' == typeof this.options?.displayId) try {
|
|
390
365
|
const stdout = await adb.shell('dumpsys display');
|
|
391
|
-
if (
|
|
366
|
+
if (this.options?.usePhysicalDisplayIdForDisplayLookup) {
|
|
392
367
|
const physicalDisplayId = await this.getPhysicalDisplayId();
|
|
393
368
|
if (physicalDisplayId) {
|
|
394
369
|
const lineRegex = new RegExp(`^.*uniqueId \"local:${physicalDisplayId}\".*$
|
|
@@ -420,8 +395,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
420
395
|
return density ?? 160;
|
|
421
396
|
}
|
|
422
397
|
async getDisplayOrientation() {
|
|
423
|
-
|
|
424
|
-
const shouldCache = !((null == (_this_options = this.options) ? void 0 : _this_options.alwaysRefreshScreenInfo) ?? false);
|
|
398
|
+
const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
|
|
425
399
|
if (shouldCache && null !== this.cachedOrientation) return this.cachedOrientation;
|
|
426
400
|
const adb = await this.getAdb();
|
|
427
401
|
let orientation = 0;
|
|
@@ -448,15 +422,15 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
448
422
|
return orientation;
|
|
449
423
|
}
|
|
450
424
|
async size() {
|
|
451
|
-
var _this_options;
|
|
452
425
|
await this.initializeDevicePixelRatio();
|
|
453
426
|
const screenSize = await this.getScreenSize();
|
|
454
427
|
const match = (screenSize.override || screenSize.physical).match(/(\d+)x(\d+)/);
|
|
455
428
|
if (!match || match.length < 3) throw new Error(`Unable to parse screen size: ${screenSize}`);
|
|
456
429
|
const isLandscape = 1 === screenSize.orientation || 3 === screenSize.orientation;
|
|
457
|
-
const
|
|
458
|
-
const
|
|
459
|
-
const
|
|
430
|
+
const shouldSwap = true !== screenSize.isCurrentOrientation && isLandscape;
|
|
431
|
+
const width = Number.parseInt(match[shouldSwap ? 2 : 1], 10);
|
|
432
|
+
const height = Number.parseInt(match[shouldSwap ? 1 : 2], 10);
|
|
433
|
+
const scale = this.options?.screenshotResizeScale ?? 1 / this.devicePixelRatio;
|
|
460
434
|
this.scalingRatio = scale;
|
|
461
435
|
const logicalWidth = Math.round(width * scale);
|
|
462
436
|
const logicalHeight = Math.round(height * scale);
|
|
@@ -497,17 +471,13 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
497
471
|
};
|
|
498
472
|
}
|
|
499
473
|
async screenshotBase64() {
|
|
500
|
-
var _this_options;
|
|
501
474
|
debugDevice('screenshotBase64 begin');
|
|
502
475
|
const adb = await this.getAdb();
|
|
503
476
|
let screenshotBuffer;
|
|
504
477
|
const androidScreenshotPath = `/data/local/tmp/midscene_screenshot_${uuid()}.png`;
|
|
505
|
-
const useShellScreencap = 'number' == typeof
|
|
478
|
+
const useShellScreencap = 'number' == typeof this.options?.displayId;
|
|
506
479
|
try {
|
|
507
|
-
if (useShellScreencap) {
|
|
508
|
-
var _this_options1;
|
|
509
|
-
throw new Error(`Display ${null == (_this_options1 = this.options) ? void 0 : _this_options1.displayId} requires shell screencap`);
|
|
510
|
-
}
|
|
480
|
+
if (useShellScreencap) throw new Error(`Display ${this.options?.displayId} requires shell screencap`);
|
|
511
481
|
debugDevice('Taking screenshot via adb.takeScreenshot');
|
|
512
482
|
screenshotBuffer = await adb.takeScreenshot(null);
|
|
513
483
|
debugDevice('adb.takeScreenshot completed');
|
|
@@ -520,9 +490,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
520
490
|
debugDevice(`Taking screenshot via adb.takeScreenshot failed or was skipped: ${error}`);
|
|
521
491
|
const screenshotPath = getTmpFile('png');
|
|
522
492
|
try {
|
|
523
|
-
var _this_options2, _this_options3;
|
|
524
493
|
debugDevice('Fallback: taking screenshot via shell screencap');
|
|
525
|
-
const displayId =
|
|
494
|
+
const displayId = this.options?.usePhysicalDisplayIdForScreenshot ? await this.getPhysicalDisplayId() : this.options?.displayId;
|
|
526
495
|
const displayArg = displayId ? `-d ${displayId}` : '';
|
|
527
496
|
try {
|
|
528
497
|
await adb.shell(`screencap -p ${displayArg} ${androidScreenshotPath}`.trim());
|
|
@@ -546,12 +515,11 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
546
515
|
return result;
|
|
547
516
|
}
|
|
548
517
|
async clearInput(element) {
|
|
549
|
-
var _this_options;
|
|
550
518
|
if (!element) return;
|
|
551
519
|
await this.ensureYadb();
|
|
552
520
|
const adb = await this.getAdb();
|
|
553
521
|
await this.mouseClick(element.center[0], element.center[1]);
|
|
554
|
-
const IME_STRATEGY = (
|
|
522
|
+
const IME_STRATEGY = (this.options?.imeStrategy || globalConfigManager.getEnvConfigValue(MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
555
523
|
if (IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII) await adb.clearTextField(100);
|
|
556
524
|
else await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboard "~CLEAR~"`);
|
|
557
525
|
if (await adb.isSoftKeyboardPresent()) return;
|
|
@@ -701,12 +669,11 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
701
669
|
}
|
|
702
670
|
}
|
|
703
671
|
async keyboardType(text, options) {
|
|
704
|
-
var _this_options, _this_options1;
|
|
705
672
|
if (!text) return;
|
|
706
673
|
const adb = await this.getAdb();
|
|
707
674
|
const isChinese = /[\p{Script=Han}\p{sc=Hani}]/u.test(text);
|
|
708
|
-
const IME_STRATEGY = (
|
|
709
|
-
const shouldAutoDismissKeyboard =
|
|
675
|
+
const IME_STRATEGY = (this.options?.imeStrategy || globalConfigManager.getEnvConfigValue(MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
676
|
+
const shouldAutoDismissKeyboard = options?.autoDismissKeyboard ?? this.options?.autoDismissKeyboard ?? true;
|
|
710
677
|
if (IME_STRATEGY === IME_STRATEGY_ALWAYS_YADB || IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII && isChinese) await this.execYadb(text);
|
|
711
678
|
else await adb.inputText(text);
|
|
712
679
|
if (true === shouldAutoDismissKeyboard) await this.hideKeyboard(options);
|
|
@@ -820,7 +787,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
820
787
|
const adb = await this.getAdb();
|
|
821
788
|
await adb.shell(`input${this.getDisplayArg()} keyevent 187`);
|
|
822
789
|
}
|
|
823
|
-
async longPress(x, y, duration =
|
|
790
|
+
async longPress(x, y, duration = 2000) {
|
|
824
791
|
const adb = await this.getAdb();
|
|
825
792
|
const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
|
|
826
793
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} ${duration}`);
|
|
@@ -866,18 +833,16 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
866
833
|
await sleep(100);
|
|
867
834
|
}
|
|
868
835
|
getDisplayArg() {
|
|
869
|
-
|
|
870
|
-
return 'number' == typeof (null == (_this_options = this.options) ? void 0 : _this_options.displayId) ? ` -d ${this.options.displayId}` : '';
|
|
836
|
+
return 'number' == typeof this.options?.displayId ? ` -d ${this.options.displayId}` : '';
|
|
871
837
|
}
|
|
872
838
|
async getPhysicalDisplayId() {
|
|
873
|
-
|
|
874
|
-
if ('number' != typeof (null == (_this_options = this.options) ? void 0 : _this_options.displayId)) return null;
|
|
839
|
+
if ('number' != typeof this.options?.displayId) return null;
|
|
875
840
|
const adb = await this.getAdb();
|
|
876
841
|
try {
|
|
877
842
|
const stdout = await adb.shell(`dumpsys SurfaceFlinger --display-id ${this.options.displayId}`);
|
|
878
843
|
const regex = new RegExp(`Display (\\d+) \\(HWC display ${this.options.displayId}\\):`);
|
|
879
844
|
const displayMatch = stdout.match(regex);
|
|
880
|
-
if (
|
|
845
|
+
if (displayMatch?.[1]) {
|
|
881
846
|
debugDevice(`Found physical display ID: ${displayMatch[1]} for display ID: ${this.options.displayId}`);
|
|
882
847
|
return displayMatch[1];
|
|
883
848
|
}
|
|
@@ -889,11 +854,10 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
889
854
|
}
|
|
890
855
|
}
|
|
891
856
|
async hideKeyboard(options, timeoutMs = 1000) {
|
|
892
|
-
var _this_options;
|
|
893
857
|
const adb = await this.getAdb();
|
|
894
|
-
const keyboardDismissStrategy =
|
|
858
|
+
const keyboardDismissStrategy = options?.keyboardDismissStrategy ?? this.options?.keyboardDismissStrategy ?? 'esc-first';
|
|
895
859
|
const keyboardStatus = await adb.isSoftKeyboardPresent();
|
|
896
|
-
const isKeyboardShown = 'boolean' == typeof keyboardStatus ? keyboardStatus :
|
|
860
|
+
const isKeyboardShown = 'boolean' == typeof keyboardStatus ? keyboardStatus : keyboardStatus?.isKeyboardShown;
|
|
897
861
|
if (!isKeyboardShown) {
|
|
898
862
|
debugDevice('Keyboard has no UI; no closing necessary');
|
|
899
863
|
return false;
|
|
@@ -912,7 +876,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
912
876
|
while(Date.now() - startTime < timeoutMs){
|
|
913
877
|
await sleep(intervalMs);
|
|
914
878
|
const currentStatus = await adb.isSoftKeyboardPresent();
|
|
915
|
-
const isStillShown = 'boolean' == typeof currentStatus ? currentStatus :
|
|
879
|
+
const isStillShown = 'boolean' == typeof currentStatus ? currentStatus : currentStatus?.isKeyboardShown;
|
|
916
880
|
if (!isStillShown) {
|
|
917
881
|
debugDevice(`Keyboard hidden successfully with keycode ${keyCode}`);
|
|
918
882
|
return true;
|
|
@@ -942,9 +906,53 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
942
906
|
node_assert(deviceId, 'deviceId is required for AndroidDevice');
|
|
943
907
|
this.deviceId = deviceId;
|
|
944
908
|
this.options = options;
|
|
945
|
-
this.customActions =
|
|
909
|
+
this.customActions = options?.customActions;
|
|
946
910
|
}
|
|
947
911
|
}
|
|
912
|
+
const runAdbShellParamSchema = z.string().describe('ADB shell command to execute');
|
|
913
|
+
const launchParamSchema = z.string().describe('App package name or URL to launch');
|
|
914
|
+
const createPlatformActions = (device)=>({
|
|
915
|
+
RunAdbShell: defineAction({
|
|
916
|
+
name: 'RunAdbShell',
|
|
917
|
+
description: 'Execute ADB shell command on Android device',
|
|
918
|
+
interfaceAlias: 'runAdbShell',
|
|
919
|
+
paramSchema: runAdbShellParamSchema,
|
|
920
|
+
call: async (param)=>{
|
|
921
|
+
const adb = await device.getAdb();
|
|
922
|
+
return await adb.shell(param);
|
|
923
|
+
}
|
|
924
|
+
}),
|
|
925
|
+
Launch: defineAction({
|
|
926
|
+
name: 'Launch',
|
|
927
|
+
description: 'Launch an Android app or URL',
|
|
928
|
+
interfaceAlias: 'launch',
|
|
929
|
+
paramSchema: launchParamSchema,
|
|
930
|
+
call: async (param)=>{
|
|
931
|
+
await device.launch(param);
|
|
932
|
+
}
|
|
933
|
+
}),
|
|
934
|
+
AndroidBackButton: defineAction({
|
|
935
|
+
name: 'AndroidBackButton',
|
|
936
|
+
description: 'Trigger the system "back" operation on Android devices',
|
|
937
|
+
call: async ()=>{
|
|
938
|
+
await device.back();
|
|
939
|
+
}
|
|
940
|
+
}),
|
|
941
|
+
AndroidHomeButton: defineAction({
|
|
942
|
+
name: 'AndroidHomeButton',
|
|
943
|
+
description: 'Trigger the system "home" operation on Android devices',
|
|
944
|
+
call: async ()=>{
|
|
945
|
+
await device.home();
|
|
946
|
+
}
|
|
947
|
+
}),
|
|
948
|
+
AndroidRecentAppsButton: defineAction({
|
|
949
|
+
name: 'AndroidRecentAppsButton',
|
|
950
|
+
description: 'Trigger the system "recent apps" operation on Android devices',
|
|
951
|
+
call: async ()=>{
|
|
952
|
+
await device.recentApps();
|
|
953
|
+
}
|
|
954
|
+
})
|
|
955
|
+
});
|
|
948
956
|
const debugUtils = getDebug('android:utils');
|
|
949
957
|
async function getConnectedDevices() {
|
|
950
958
|
try {
|
|
@@ -961,35 +969,39 @@ async function getConnectedDevices() {
|
|
|
961
969
|
});
|
|
962
970
|
}
|
|
963
971
|
}
|
|
972
|
+
function agent_define_property(obj, key, value) {
|
|
973
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
974
|
+
value: value,
|
|
975
|
+
enumerable: true,
|
|
976
|
+
configurable: true,
|
|
977
|
+
writable: true
|
|
978
|
+
});
|
|
979
|
+
else obj[key] = value;
|
|
980
|
+
return obj;
|
|
981
|
+
}
|
|
964
982
|
const debugAgent = getDebug('android:agent');
|
|
965
983
|
class AndroidAgent extends Agent {
|
|
966
|
-
|
|
967
|
-
const
|
|
968
|
-
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
984
|
+
createActionWrapper(name) {
|
|
985
|
+
const action = this.wrapActionInActionSpace(name);
|
|
986
|
+
return (...args)=>action(args[0]);
|
|
987
|
+
}
|
|
988
|
+
constructor(device, opts){
|
|
989
|
+
super(device, opts), agent_define_property(this, "launch", void 0), agent_define_property(this, "runAdbShell", void 0), agent_define_property(this, "back", void 0), agent_define_property(this, "home", void 0), agent_define_property(this, "recentApps", void 0);
|
|
990
|
+
this.launch = this.createActionWrapper('Launch');
|
|
991
|
+
this.runAdbShell = this.createActionWrapper('RunAdbShell');
|
|
992
|
+
this.back = this.createActionWrapper('AndroidBackButton');
|
|
993
|
+
this.home = this.createActionWrapper('AndroidHomeButton');
|
|
994
|
+
this.recentApps = this.createActionWrapper('AndroidRecentAppsButton');
|
|
973
995
|
}
|
|
974
996
|
}
|
|
975
997
|
async function agentFromAdbDevice(deviceId, opts) {
|
|
976
998
|
if (!deviceId) {
|
|
977
999
|
const devices = await getConnectedDevices();
|
|
1000
|
+
if (0 === devices.length) throw new Error('No Android devices found. Please connect an Android device and ensure ADB is properly configured. Run `adb devices` to verify device connection.');
|
|
978
1001
|
deviceId = devices[0].udid;
|
|
979
1002
|
debugAgent('deviceId not specified, will use the first device (id = %s)', deviceId);
|
|
980
1003
|
}
|
|
981
|
-
const device = new AndroidDevice(deviceId, {
|
|
982
|
-
autoDismissKeyboard: null == opts ? void 0 : opts.autoDismissKeyboard,
|
|
983
|
-
androidAdbPath: null == opts ? void 0 : opts.androidAdbPath,
|
|
984
|
-
remoteAdbHost: null == opts ? void 0 : opts.remoteAdbHost,
|
|
985
|
-
remoteAdbPort: null == opts ? void 0 : opts.remoteAdbPort,
|
|
986
|
-
imeStrategy: null == opts ? void 0 : opts.imeStrategy,
|
|
987
|
-
displayId: null == opts ? void 0 : opts.displayId,
|
|
988
|
-
usePhysicalDisplayIdForScreenshot: null == opts ? void 0 : opts.usePhysicalDisplayIdForScreenshot,
|
|
989
|
-
usePhysicalDisplayIdForDisplayLookup: null == opts ? void 0 : opts.usePhysicalDisplayIdForDisplayLookup,
|
|
990
|
-
screenshotResizeScale: null == opts ? void 0 : opts.screenshotResizeScale,
|
|
991
|
-
alwaysRefreshScreenInfo: null == opts ? void 0 : opts.alwaysRefreshScreenInfo
|
|
992
|
-
});
|
|
1004
|
+
const device = new AndroidDevice(deviceId, opts || {});
|
|
993
1005
|
await device.connect();
|
|
994
1006
|
return new AndroidAgent(device, opts);
|
|
995
1007
|
}
|
package/dist/lib/index.js
CHANGED
|
@@ -37,10 +37,10 @@ var __webpack_exports__ = {};
|
|
|
37
37
|
__webpack_require__.r(__webpack_exports__);
|
|
38
38
|
__webpack_require__.d(__webpack_exports__, {
|
|
39
39
|
getConnectedDevices: ()=>getConnectedDevices,
|
|
40
|
-
agentFromAdbDevice: ()=>agentFromAdbDevice,
|
|
41
|
-
AndroidDevice: ()=>AndroidDevice,
|
|
42
40
|
overrideAIConfig: ()=>env_namespaceObject.overrideAIConfig,
|
|
43
|
-
AndroidAgent: ()=>AndroidAgent
|
|
41
|
+
AndroidAgent: ()=>AndroidAgent,
|
|
42
|
+
agentFromAdbDevice: ()=>agentFromAdbDevice,
|
|
43
|
+
AndroidDevice: ()=>AndroidDevice
|
|
44
44
|
});
|
|
45
45
|
const external_node_assert_namespaceObject = require("node:assert");
|
|
46
46
|
var external_node_assert_default = /*#__PURE__*/ __webpack_require__.n(external_node_assert_namespaceObject);
|
|
@@ -101,14 +101,13 @@ class AndroidDevice {
|
|
|
101
101
|
locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The input field to be filled').optional()
|
|
102
102
|
}),
|
|
103
103
|
call: async (param)=>{
|
|
104
|
-
var _this_options;
|
|
105
104
|
const element = param.locate;
|
|
106
105
|
if (element) {
|
|
107
106
|
if ('append' !== param.mode) await this.clearInput(element);
|
|
108
107
|
}
|
|
109
108
|
if ('clear' === param.mode) return;
|
|
110
109
|
if (!param || !param.value) return;
|
|
111
|
-
const autoDismissKeyboard = param.autoDismissKeyboard ??
|
|
110
|
+
const autoDismissKeyboard = param.autoDismissKeyboard ?? this.options?.autoDismissKeyboard;
|
|
112
111
|
await this.keyboardType(param.value, {
|
|
113
112
|
autoDismissKeyboard
|
|
114
113
|
});
|
|
@@ -120,18 +119,18 @@ class AndroidDevice {
|
|
|
120
119
|
left: element.center[0],
|
|
121
120
|
top: element.center[1]
|
|
122
121
|
} : void 0;
|
|
123
|
-
const scrollToEventName =
|
|
124
|
-
if ('
|
|
125
|
-
else if ('
|
|
126
|
-
else if ('
|
|
127
|
-
else if ('
|
|
128
|
-
else if ('
|
|
122
|
+
const scrollToEventName = param?.scrollType;
|
|
123
|
+
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
124
|
+
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
125
|
+
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
126
|
+
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
127
|
+
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
129
128
|
else {
|
|
130
|
-
if (
|
|
129
|
+
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
|
|
131
130
|
else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
|
|
132
131
|
else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
|
|
133
132
|
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
134
|
-
else await this.scrollDown(
|
|
133
|
+
else await this.scrollDown(param?.distance || void 0, startingPoint);
|
|
135
134
|
await (0, utils_namespaceObject.sleep)(500);
|
|
136
135
|
}
|
|
137
136
|
}),
|
|
@@ -151,30 +150,6 @@ class AndroidDevice {
|
|
|
151
150
|
(0, device_namespaceObject.defineActionKeyboardPress)(async (param)=>{
|
|
152
151
|
await this.keyboardPress(param.keyName);
|
|
153
152
|
}),
|
|
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
153
|
(0, device_namespaceObject.defineAction)({
|
|
179
154
|
name: 'AndroidLongPress',
|
|
180
155
|
description: 'Trigger a long press on the screen at specified coordinates on Android devices',
|
|
@@ -186,7 +161,7 @@ class AndroidDevice {
|
|
|
186
161
|
const element = param.locate;
|
|
187
162
|
if (!element) throw new Error('AndroidLongPress requires an element to be located');
|
|
188
163
|
const [x, y] = element.center;
|
|
189
|
-
await this.longPress(x, y,
|
|
164
|
+
await this.longPress(x, y, param?.duration);
|
|
190
165
|
}
|
|
191
166
|
}),
|
|
192
167
|
(0, device_namespaceObject.defineAction)({
|
|
@@ -219,9 +194,11 @@ class AndroidDevice {
|
|
|
219
194
|
await this.clearInput(element);
|
|
220
195
|
})
|
|
221
196
|
];
|
|
197
|
+
const platformSpecificActions = Object.values(createPlatformActions(this));
|
|
222
198
|
const customActions = this.customActions || [];
|
|
223
199
|
return [
|
|
224
200
|
...defaultActions,
|
|
201
|
+
...platformSpecificActions,
|
|
225
202
|
...customActions
|
|
226
203
|
];
|
|
227
204
|
}
|
|
@@ -239,10 +216,9 @@ class AndroidDevice {
|
|
|
239
216
|
let error = null;
|
|
240
217
|
debugDevice(`Initializing ADB with device ID: ${this.deviceId}`);
|
|
241
218
|
try {
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
const remoteAdbPort = (null == (_this_options2 = this.options) ? void 0 : _this_options2.remoteAdbPort) || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ADB_REMOTE_PORT);
|
|
219
|
+
const androidAdbPath = this.options?.androidAdbPath || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ADB_PATH);
|
|
220
|
+
const remoteAdbHost = this.options?.remoteAdbHost || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ADB_REMOTE_HOST);
|
|
221
|
+
const remoteAdbPort = this.options?.remoteAdbPort || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ADB_REMOTE_PORT);
|
|
246
222
|
this.adb = await new external_appium_adb_namespaceObject.ADB({
|
|
247
223
|
udid: this.deviceId,
|
|
248
224
|
adbExecTimeout: 60000,
|
|
@@ -257,7 +233,7 @@ class AndroidDevice {
|
|
|
257
233
|
this.description = `
|
|
258
234
|
DeviceId: ${this.deviceId}
|
|
259
235
|
ScreenSize:
|
|
260
|
-
${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[key]}${'override' === key && size[key] ?
|
|
236
|
+
${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[key]}${'override' === key && size[key] ? ' ✅' : ''}`).join('\n')}
|
|
261
237
|
`;
|
|
262
238
|
debugDevice('ADB initialized successfully', this.description);
|
|
263
239
|
return this.adb;
|
|
@@ -332,14 +308,12 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
332
308
|
};
|
|
333
309
|
}
|
|
334
310
|
async getScreenSize() {
|
|
335
|
-
|
|
336
|
-
const shouldCache = !((null == (_this_options = this.options) ? void 0 : _this_options.alwaysRefreshScreenInfo) ?? false);
|
|
311
|
+
const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
|
|
337
312
|
if (shouldCache && this.cachedScreenSize) return this.cachedScreenSize;
|
|
338
313
|
const adb = await this.getAdb();
|
|
339
|
-
if ('number' == typeof
|
|
340
|
-
var _this_options2;
|
|
314
|
+
if ('number' == typeof this.options?.displayId) try {
|
|
341
315
|
const stdout = await adb.shell('dumpsys display');
|
|
342
|
-
if (
|
|
316
|
+
if (this.options?.usePhysicalDisplayIdForDisplayLookup) {
|
|
343
317
|
const physicalDisplayId = await this.getPhysicalDisplayId();
|
|
344
318
|
if (physicalDisplayId) {
|
|
345
319
|
const lineRegex = new RegExp(`^.*uniqueId \"local:${physicalDisplayId}\".*$
|
|
@@ -358,7 +332,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
358
332
|
const result = {
|
|
359
333
|
override: sizeStr,
|
|
360
334
|
physical: sizeStr,
|
|
361
|
-
orientation: rotation
|
|
335
|
+
orientation: rotation,
|
|
336
|
+
isCurrentOrientation: true
|
|
362
337
|
};
|
|
363
338
|
if (shouldCache) this.cachedScreenSize = result;
|
|
364
339
|
return result;
|
|
@@ -381,7 +356,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
381
356
|
const result = {
|
|
382
357
|
override: sizeStr,
|
|
383
358
|
physical: sizeStr,
|
|
384
|
-
orientation: rotation
|
|
359
|
+
orientation: rotation,
|
|
360
|
+
isCurrentOrientation: true
|
|
385
361
|
};
|
|
386
362
|
if (shouldCache) this.cachedScreenSize = result;
|
|
387
363
|
return result;
|
|
@@ -414,7 +390,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
414
390
|
if (size.override || size.physical) {
|
|
415
391
|
const result = {
|
|
416
392
|
...size,
|
|
417
|
-
orientation
|
|
393
|
+
orientation,
|
|
394
|
+
isCurrentOrientation: false
|
|
418
395
|
};
|
|
419
396
|
if (shouldCache) this.cachedScreenSize = result;
|
|
420
397
|
return result;
|
|
@@ -429,12 +406,10 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
429
406
|
this.devicePixelRatioInitialized = true;
|
|
430
407
|
}
|
|
431
408
|
async getDisplayDensity() {
|
|
432
|
-
var _this_options;
|
|
433
409
|
const adb = await this.getAdb();
|
|
434
|
-
if ('number' == typeof
|
|
435
|
-
var _this_options1;
|
|
410
|
+
if ('number' == typeof this.options?.displayId) try {
|
|
436
411
|
const stdout = await adb.shell('dumpsys display');
|
|
437
|
-
if (
|
|
412
|
+
if (this.options?.usePhysicalDisplayIdForDisplayLookup) {
|
|
438
413
|
const physicalDisplayId = await this.getPhysicalDisplayId();
|
|
439
414
|
if (physicalDisplayId) {
|
|
440
415
|
const lineRegex = new RegExp(`^.*uniqueId \"local:${physicalDisplayId}\".*$
|
|
@@ -466,8 +441,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
466
441
|
return density ?? 160;
|
|
467
442
|
}
|
|
468
443
|
async getDisplayOrientation() {
|
|
469
|
-
|
|
470
|
-
const shouldCache = !((null == (_this_options = this.options) ? void 0 : _this_options.alwaysRefreshScreenInfo) ?? false);
|
|
444
|
+
const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
|
|
471
445
|
if (shouldCache && null !== this.cachedOrientation) return this.cachedOrientation;
|
|
472
446
|
const adb = await this.getAdb();
|
|
473
447
|
let orientation = 0;
|
|
@@ -494,15 +468,15 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
494
468
|
return orientation;
|
|
495
469
|
}
|
|
496
470
|
async size() {
|
|
497
|
-
var _this_options;
|
|
498
471
|
await this.initializeDevicePixelRatio();
|
|
499
472
|
const screenSize = await this.getScreenSize();
|
|
500
473
|
const match = (screenSize.override || screenSize.physical).match(/(\d+)x(\d+)/);
|
|
501
474
|
if (!match || match.length < 3) throw new Error(`Unable to parse screen size: ${screenSize}`);
|
|
502
475
|
const isLandscape = 1 === screenSize.orientation || 3 === screenSize.orientation;
|
|
503
|
-
const
|
|
504
|
-
const
|
|
505
|
-
const
|
|
476
|
+
const shouldSwap = true !== screenSize.isCurrentOrientation && isLandscape;
|
|
477
|
+
const width = Number.parseInt(match[shouldSwap ? 2 : 1], 10);
|
|
478
|
+
const height = Number.parseInt(match[shouldSwap ? 1 : 2], 10);
|
|
479
|
+
const scale = this.options?.screenshotResizeScale ?? 1 / this.devicePixelRatio;
|
|
506
480
|
this.scalingRatio = scale;
|
|
507
481
|
const logicalWidth = Math.round(width * scale);
|
|
508
482
|
const logicalHeight = Math.round(height * scale);
|
|
@@ -543,17 +517,13 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
543
517
|
};
|
|
544
518
|
}
|
|
545
519
|
async screenshotBase64() {
|
|
546
|
-
var _this_options;
|
|
547
520
|
debugDevice('screenshotBase64 begin');
|
|
548
521
|
const adb = await this.getAdb();
|
|
549
522
|
let screenshotBuffer;
|
|
550
523
|
const androidScreenshotPath = `/data/local/tmp/midscene_screenshot_${(0, shared_utils_namespaceObject.uuid)()}.png`;
|
|
551
|
-
const useShellScreencap = 'number' == typeof
|
|
524
|
+
const useShellScreencap = 'number' == typeof this.options?.displayId;
|
|
552
525
|
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
|
-
}
|
|
526
|
+
if (useShellScreencap) throw new Error(`Display ${this.options?.displayId} requires shell screencap`);
|
|
557
527
|
debugDevice('Taking screenshot via adb.takeScreenshot');
|
|
558
528
|
screenshotBuffer = await adb.takeScreenshot(null);
|
|
559
529
|
debugDevice('adb.takeScreenshot completed');
|
|
@@ -566,9 +536,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
566
536
|
debugDevice(`Taking screenshot via adb.takeScreenshot failed or was skipped: ${error}`);
|
|
567
537
|
const screenshotPath = (0, utils_namespaceObject.getTmpFile)('png');
|
|
568
538
|
try {
|
|
569
|
-
var _this_options2, _this_options3;
|
|
570
539
|
debugDevice('Fallback: taking screenshot via shell screencap');
|
|
571
|
-
const displayId =
|
|
540
|
+
const displayId = this.options?.usePhysicalDisplayIdForScreenshot ? await this.getPhysicalDisplayId() : this.options?.displayId;
|
|
572
541
|
const displayArg = displayId ? `-d ${displayId}` : '';
|
|
573
542
|
try {
|
|
574
543
|
await adb.shell(`screencap -p ${displayArg} ${androidScreenshotPath}`.trim());
|
|
@@ -592,12 +561,11 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
592
561
|
return result;
|
|
593
562
|
}
|
|
594
563
|
async clearInput(element) {
|
|
595
|
-
var _this_options;
|
|
596
564
|
if (!element) return;
|
|
597
565
|
await this.ensureYadb();
|
|
598
566
|
const adb = await this.getAdb();
|
|
599
567
|
await this.mouseClick(element.center[0], element.center[1]);
|
|
600
|
-
const IME_STRATEGY = (
|
|
568
|
+
const IME_STRATEGY = (this.options?.imeStrategy || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
601
569
|
if (IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII) await adb.clearTextField(100);
|
|
602
570
|
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
571
|
if (await adb.isSoftKeyboardPresent()) return;
|
|
@@ -747,12 +715,11 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
747
715
|
}
|
|
748
716
|
}
|
|
749
717
|
async keyboardType(text, options) {
|
|
750
|
-
var _this_options, _this_options1;
|
|
751
718
|
if (!text) return;
|
|
752
719
|
const adb = await this.getAdb();
|
|
753
720
|
const isChinese = /[\p{Script=Han}\p{sc=Hani}]/u.test(text);
|
|
754
|
-
const IME_STRATEGY = (
|
|
755
|
-
const shouldAutoDismissKeyboard =
|
|
721
|
+
const IME_STRATEGY = (this.options?.imeStrategy || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
722
|
+
const shouldAutoDismissKeyboard = options?.autoDismissKeyboard ?? this.options?.autoDismissKeyboard ?? true;
|
|
756
723
|
if (IME_STRATEGY === IME_STRATEGY_ALWAYS_YADB || IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII && isChinese) await this.execYadb(text);
|
|
757
724
|
else await adb.inputText(text);
|
|
758
725
|
if (true === shouldAutoDismissKeyboard) await this.hideKeyboard(options);
|
|
@@ -866,7 +833,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
866
833
|
const adb = await this.getAdb();
|
|
867
834
|
await adb.shell(`input${this.getDisplayArg()} keyevent 187`);
|
|
868
835
|
}
|
|
869
|
-
async longPress(x, y, duration =
|
|
836
|
+
async longPress(x, y, duration = 2000) {
|
|
870
837
|
const adb = await this.getAdb();
|
|
871
838
|
const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
|
|
872
839
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} ${duration}`);
|
|
@@ -912,18 +879,16 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
912
879
|
await (0, utils_namespaceObject.sleep)(100);
|
|
913
880
|
}
|
|
914
881
|
getDisplayArg() {
|
|
915
|
-
|
|
916
|
-
return 'number' == typeof (null == (_this_options = this.options) ? void 0 : _this_options.displayId) ? ` -d ${this.options.displayId}` : '';
|
|
882
|
+
return 'number' == typeof this.options?.displayId ? ` -d ${this.options.displayId}` : '';
|
|
917
883
|
}
|
|
918
884
|
async getPhysicalDisplayId() {
|
|
919
|
-
|
|
920
|
-
if ('number' != typeof (null == (_this_options = this.options) ? void 0 : _this_options.displayId)) return null;
|
|
885
|
+
if ('number' != typeof this.options?.displayId) return null;
|
|
921
886
|
const adb = await this.getAdb();
|
|
922
887
|
try {
|
|
923
888
|
const stdout = await adb.shell(`dumpsys SurfaceFlinger --display-id ${this.options.displayId}`);
|
|
924
889
|
const regex = new RegExp(`Display (\\d+) \\(HWC display ${this.options.displayId}\\):`);
|
|
925
890
|
const displayMatch = stdout.match(regex);
|
|
926
|
-
if (
|
|
891
|
+
if (displayMatch?.[1]) {
|
|
927
892
|
debugDevice(`Found physical display ID: ${displayMatch[1]} for display ID: ${this.options.displayId}`);
|
|
928
893
|
return displayMatch[1];
|
|
929
894
|
}
|
|
@@ -935,11 +900,10 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
935
900
|
}
|
|
936
901
|
}
|
|
937
902
|
async hideKeyboard(options, timeoutMs = 1000) {
|
|
938
|
-
var _this_options;
|
|
939
903
|
const adb = await this.getAdb();
|
|
940
|
-
const keyboardDismissStrategy =
|
|
904
|
+
const keyboardDismissStrategy = options?.keyboardDismissStrategy ?? this.options?.keyboardDismissStrategy ?? 'esc-first';
|
|
941
905
|
const keyboardStatus = await adb.isSoftKeyboardPresent();
|
|
942
|
-
const isKeyboardShown = 'boolean' == typeof keyboardStatus ? keyboardStatus :
|
|
906
|
+
const isKeyboardShown = 'boolean' == typeof keyboardStatus ? keyboardStatus : keyboardStatus?.isKeyboardShown;
|
|
943
907
|
if (!isKeyboardShown) {
|
|
944
908
|
debugDevice('Keyboard has no UI; no closing necessary');
|
|
945
909
|
return false;
|
|
@@ -958,7 +922,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
958
922
|
while(Date.now() - startTime < timeoutMs){
|
|
959
923
|
await (0, utils_namespaceObject.sleep)(intervalMs);
|
|
960
924
|
const currentStatus = await adb.isSoftKeyboardPresent();
|
|
961
|
-
const isStillShown = 'boolean' == typeof currentStatus ? currentStatus :
|
|
925
|
+
const isStillShown = 'boolean' == typeof currentStatus ? currentStatus : currentStatus?.isKeyboardShown;
|
|
962
926
|
if (!isStillShown) {
|
|
963
927
|
debugDevice(`Keyboard hidden successfully with keycode ${keyCode}`);
|
|
964
928
|
return true;
|
|
@@ -988,9 +952,53 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
988
952
|
external_node_assert_default()(deviceId, 'deviceId is required for AndroidDevice');
|
|
989
953
|
this.deviceId = deviceId;
|
|
990
954
|
this.options = options;
|
|
991
|
-
this.customActions =
|
|
955
|
+
this.customActions = options?.customActions;
|
|
992
956
|
}
|
|
993
957
|
}
|
|
958
|
+
const runAdbShellParamSchema = core_namespaceObject.z.string().describe('ADB shell command to execute');
|
|
959
|
+
const launchParamSchema = core_namespaceObject.z.string().describe('App package name or URL to launch');
|
|
960
|
+
const createPlatformActions = (device)=>({
|
|
961
|
+
RunAdbShell: (0, device_namespaceObject.defineAction)({
|
|
962
|
+
name: 'RunAdbShell',
|
|
963
|
+
description: 'Execute ADB shell command on Android device',
|
|
964
|
+
interfaceAlias: 'runAdbShell',
|
|
965
|
+
paramSchema: runAdbShellParamSchema,
|
|
966
|
+
call: async (param)=>{
|
|
967
|
+
const adb = await device.getAdb();
|
|
968
|
+
return await adb.shell(param);
|
|
969
|
+
}
|
|
970
|
+
}),
|
|
971
|
+
Launch: (0, device_namespaceObject.defineAction)({
|
|
972
|
+
name: 'Launch',
|
|
973
|
+
description: 'Launch an Android app or URL',
|
|
974
|
+
interfaceAlias: 'launch',
|
|
975
|
+
paramSchema: launchParamSchema,
|
|
976
|
+
call: async (param)=>{
|
|
977
|
+
await device.launch(param);
|
|
978
|
+
}
|
|
979
|
+
}),
|
|
980
|
+
AndroidBackButton: (0, device_namespaceObject.defineAction)({
|
|
981
|
+
name: 'AndroidBackButton',
|
|
982
|
+
description: 'Trigger the system "back" operation on Android devices',
|
|
983
|
+
call: async ()=>{
|
|
984
|
+
await device.back();
|
|
985
|
+
}
|
|
986
|
+
}),
|
|
987
|
+
AndroidHomeButton: (0, device_namespaceObject.defineAction)({
|
|
988
|
+
name: 'AndroidHomeButton',
|
|
989
|
+
description: 'Trigger the system "home" operation on Android devices',
|
|
990
|
+
call: async ()=>{
|
|
991
|
+
await device.home();
|
|
992
|
+
}
|
|
993
|
+
}),
|
|
994
|
+
AndroidRecentAppsButton: (0, device_namespaceObject.defineAction)({
|
|
995
|
+
name: 'AndroidRecentAppsButton',
|
|
996
|
+
description: 'Trigger the system "recent apps" operation on Android devices',
|
|
997
|
+
call: async ()=>{
|
|
998
|
+
await device.recentApps();
|
|
999
|
+
}
|
|
1000
|
+
})
|
|
1001
|
+
});
|
|
994
1002
|
const agent_namespaceObject = require("@midscene/core/agent");
|
|
995
1003
|
const debugUtils = (0, logger_namespaceObject.getDebug)('android:utils');
|
|
996
1004
|
async function getConnectedDevices() {
|
|
@@ -1008,35 +1016,39 @@ async function getConnectedDevices() {
|
|
|
1008
1016
|
});
|
|
1009
1017
|
}
|
|
1010
1018
|
}
|
|
1019
|
+
function agent_define_property(obj, key, value) {
|
|
1020
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
1021
|
+
value: value,
|
|
1022
|
+
enumerable: true,
|
|
1023
|
+
configurable: true,
|
|
1024
|
+
writable: true
|
|
1025
|
+
});
|
|
1026
|
+
else obj[key] = value;
|
|
1027
|
+
return obj;
|
|
1028
|
+
}
|
|
1011
1029
|
const debugAgent = (0, logger_namespaceObject.getDebug)('android:agent');
|
|
1012
1030
|
class AndroidAgent extends agent_namespaceObject.Agent {
|
|
1013
|
-
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1031
|
+
createActionWrapper(name) {
|
|
1032
|
+
const action = this.wrapActionInActionSpace(name);
|
|
1033
|
+
return (...args)=>action(args[0]);
|
|
1016
1034
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1035
|
+
constructor(device, opts){
|
|
1036
|
+
super(device, opts), agent_define_property(this, "launch", void 0), agent_define_property(this, "runAdbShell", void 0), agent_define_property(this, "back", void 0), agent_define_property(this, "home", void 0), agent_define_property(this, "recentApps", void 0);
|
|
1037
|
+
this.launch = this.createActionWrapper('Launch');
|
|
1038
|
+
this.runAdbShell = this.createActionWrapper('RunAdbShell');
|
|
1039
|
+
this.back = this.createActionWrapper('AndroidBackButton');
|
|
1040
|
+
this.home = this.createActionWrapper('AndroidHomeButton');
|
|
1041
|
+
this.recentApps = this.createActionWrapper('AndroidRecentAppsButton');
|
|
1020
1042
|
}
|
|
1021
1043
|
}
|
|
1022
1044
|
async function agentFromAdbDevice(deviceId, opts) {
|
|
1023
1045
|
if (!deviceId) {
|
|
1024
1046
|
const devices = await getConnectedDevices();
|
|
1047
|
+
if (0 === devices.length) throw new Error('No Android devices found. Please connect an Android device and ensure ADB is properly configured. Run `adb devices` to verify device connection.');
|
|
1025
1048
|
deviceId = devices[0].udid;
|
|
1026
1049
|
debugAgent('deviceId not specified, will use the first device (id = %s)', deviceId);
|
|
1027
1050
|
}
|
|
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
|
-
});
|
|
1051
|
+
const device = new AndroidDevice(deviceId, opts || {});
|
|
1040
1052
|
await device.connect();
|
|
1041
1053
|
return new AndroidAgent(device, opts);
|
|
1042
1054
|
}
|
|
@@ -1045,13 +1057,13 @@ exports.AndroidDevice = __webpack_exports__.AndroidDevice;
|
|
|
1045
1057
|
exports.agentFromAdbDevice = __webpack_exports__.agentFromAdbDevice;
|
|
1046
1058
|
exports.getConnectedDevices = __webpack_exports__.getConnectedDevices;
|
|
1047
1059
|
exports.overrideAIConfig = __webpack_exports__.overrideAIConfig;
|
|
1048
|
-
for(var
|
|
1060
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
1049
1061
|
"AndroidAgent",
|
|
1050
1062
|
"AndroidDevice",
|
|
1051
1063
|
"agentFromAdbDevice",
|
|
1052
1064
|
"getConnectedDevices",
|
|
1053
1065
|
"overrideAIConfig"
|
|
1054
|
-
].indexOf(
|
|
1066
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
1055
1067
|
Object.defineProperty(exports, '__esModule', {
|
|
1056
1068
|
value: true
|
|
1057
1069
|
});
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { AbstractInterface } from '@midscene/core/device';
|
|
2
|
+
import type { ActionParam } from '@midscene/core';
|
|
3
|
+
import type { ActionReturn } from '@midscene/core';
|
|
2
4
|
import { ADB } from 'appium-adb';
|
|
3
5
|
import { Agent } from '@midscene/core/agent';
|
|
4
6
|
import { AgentOpt } from '@midscene/core/agent';
|
|
7
|
+
import { AndroidDeviceInputOpt } from '@midscene/core/device';
|
|
8
|
+
import { AndroidDeviceOpt } from '@midscene/core/device';
|
|
5
9
|
import { Device } from 'appium-adb';
|
|
6
10
|
import { DeviceAction } from '@midscene/core';
|
|
7
11
|
import type { ElementInfo } from '@midscene/shared/extractor';
|
|
@@ -9,15 +13,38 @@ import { InterfaceType } from '@midscene/core';
|
|
|
9
13
|
import { overrideAIConfig } from '@midscene/shared/env';
|
|
10
14
|
import { Point } from '@midscene/core';
|
|
11
15
|
import { Size } from '@midscene/core';
|
|
16
|
+
import { z } from '@midscene/core';
|
|
17
|
+
|
|
18
|
+
declare type ActionArgs<T extends DeviceAction> = [ActionParam<T>] extends [undefined] ? [] : [ActionParam<T>];
|
|
12
19
|
|
|
13
20
|
export declare function agentFromAdbDevice(deviceId?: string, opts?: AndroidAgentOpt & AndroidDeviceOpt): Promise<AndroidAgent>;
|
|
14
21
|
|
|
15
22
|
export declare class AndroidAgent extends Agent<AndroidDevice> {
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Launch an Android app or URL
|
|
25
|
+
*/
|
|
26
|
+
launch: WrappedAction<DeviceActionLaunch>;
|
|
27
|
+
/**
|
|
28
|
+
* Execute ADB shell command on Android device
|
|
29
|
+
*/
|
|
30
|
+
runAdbShell: WrappedAction<DeviceActionRunAdbShell>;
|
|
31
|
+
/**
|
|
32
|
+
* Trigger the system back operation on Android devices
|
|
33
|
+
*/
|
|
34
|
+
back: WrappedAction<DeviceActionAndroidBackButton>;
|
|
35
|
+
/**
|
|
36
|
+
* Trigger the system home operation on Android devices
|
|
37
|
+
*/
|
|
38
|
+
home: WrappedAction<DeviceActionAndroidHomeButton>;
|
|
39
|
+
/**
|
|
40
|
+
* Trigger the system recent apps operation on Android devices
|
|
41
|
+
*/
|
|
42
|
+
recentApps: WrappedAction<DeviceActionAndroidRecentAppsButton>;
|
|
43
|
+
constructor(device: AndroidDevice, opts?: AndroidAgentOpt);
|
|
44
|
+
private createActionWrapper;
|
|
18
45
|
}
|
|
19
46
|
|
|
20
|
-
declare type AndroidAgentOpt = AgentOpt;
|
|
47
|
+
export declare type AndroidAgentOpt = AgentOpt;
|
|
21
48
|
|
|
22
49
|
export declare class AndroidDevice implements AbstractInterface {
|
|
23
50
|
private deviceId;
|
|
@@ -49,6 +76,7 @@ export declare class AndroidDevice implements AbstractInterface {
|
|
|
49
76
|
override: string;
|
|
50
77
|
physical: string;
|
|
51
78
|
orientation: number;
|
|
79
|
+
isCurrentOrientation?: boolean;
|
|
52
80
|
}>;
|
|
53
81
|
private initializeDevicePixelRatio;
|
|
54
82
|
getDisplayDensity(): Promise<number>;
|
|
@@ -114,32 +142,35 @@ export declare class AndroidDevice implements AbstractInterface {
|
|
|
114
142
|
hideKeyboard(options?: AndroidDeviceInputOpt, timeoutMs?: number): Promise<boolean>;
|
|
115
143
|
}
|
|
116
144
|
|
|
117
|
-
declare type
|
|
118
|
-
autoDismissKeyboard?: boolean;
|
|
119
|
-
keyboardDismissStrategy?: 'esc-first' | 'back-first';
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
declare type AndroidDeviceOpt = {
|
|
123
|
-
androidAdbPath?: string;
|
|
124
|
-
remoteAdbHost?: string;
|
|
125
|
-
remoteAdbPort?: number;
|
|
126
|
-
imeStrategy?: ImeStrategy;
|
|
127
|
-
displayId?: number;
|
|
128
|
-
usePhysicalDisplayIdForScreenshot?: boolean;
|
|
129
|
-
usePhysicalDisplayIdForDisplayLookup?: boolean;
|
|
130
|
-
customActions?: DeviceAction<any>[];
|
|
131
|
-
screenshotResizeScale?: number;
|
|
132
|
-
alwaysRefreshScreenInfo?: boolean;
|
|
133
|
-
} & AndroidDeviceInputOpt;
|
|
145
|
+
declare type DeviceActionAndroidBackButton = DeviceAction<undefined, void>;
|
|
134
146
|
|
|
135
|
-
|
|
147
|
+
declare type DeviceActionAndroidHomeButton = DeviceAction<undefined, void>;
|
|
148
|
+
|
|
149
|
+
declare type DeviceActionAndroidRecentAppsButton = DeviceAction<undefined, void>;
|
|
136
150
|
|
|
137
|
-
declare
|
|
151
|
+
declare type DeviceActionLaunch = DeviceAction<LaunchParam, void>;
|
|
138
152
|
|
|
139
|
-
declare
|
|
153
|
+
declare type DeviceActionRunAdbShell = DeviceAction<RunAdbShellParam, string>;
|
|
154
|
+
|
|
155
|
+
export declare function getConnectedDevices(): Promise<Device[]>;
|
|
140
156
|
|
|
141
|
-
declare type
|
|
157
|
+
declare type LaunchParam = z.infer<typeof launchParamSchema>;
|
|
158
|
+
|
|
159
|
+
declare const launchParamSchema: z.ZodString;
|
|
142
160
|
|
|
143
161
|
export { overrideAIConfig }
|
|
144
162
|
|
|
163
|
+
declare type RunAdbShellParam = z.infer<typeof runAdbShellParamSchema>;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Platform-specific action definitions for Android
|
|
167
|
+
* Single source of truth for both runtime behavior and type definitions
|
|
168
|
+
*/
|
|
169
|
+
declare const runAdbShellParamSchema: z.ZodString;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Helper type to convert DeviceAction to wrapped method signature
|
|
173
|
+
*/
|
|
174
|
+
declare type WrappedAction<T extends DeviceAction> = (...args: ActionArgs<T>) => Promise<ActionReturn<T>>;
|
|
175
|
+
|
|
145
176
|
export { }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@midscene/android",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.11-beta-20251218071621.0",
|
|
4
4
|
"description": "Android automation library for Midscene",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Android UI automation",
|
|
@@ -27,23 +27,24 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"appium-adb": "12.12.1",
|
|
30
|
-
"@midscene/core": "0.30.
|
|
31
|
-
"@midscene/shared": "0.30.
|
|
30
|
+
"@midscene/core": "0.30.11-beta-20251218071621.0",
|
|
31
|
+
"@midscene/shared": "0.30.11-beta-20251218071621.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@rslib/core": "^0.
|
|
34
|
+
"@rslib/core": "^0.18.3",
|
|
35
35
|
"@types/node": "^18.0.0",
|
|
36
36
|
"dotenv": "^16.4.5",
|
|
37
37
|
"typescript": "^5.8.3",
|
|
38
38
|
"tsx": "^4.19.2",
|
|
39
39
|
"vitest": "3.0.5",
|
|
40
|
-
"
|
|
40
|
+
"zod": "3.24.3",
|
|
41
|
+
"@midscene/playground": "0.30.11-beta-20251218071621.0"
|
|
41
42
|
},
|
|
42
43
|
"license": "MIT",
|
|
43
44
|
"scripts": {
|
|
44
45
|
"dev": "npm run build:watch",
|
|
45
46
|
"build": "rslib build",
|
|
46
|
-
"build:watch": "rslib build --watch",
|
|
47
|
+
"build:watch": "rslib build --watch --no-clean",
|
|
47
48
|
"playground": "DEBUG=midscene:* tsx demo/playground.ts",
|
|
48
49
|
"test": "vitest --run",
|
|
49
50
|
"test:u": "vitest --run -u",
|