@midscene/android 1.8.0 → 1.8.1
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/cli.mjs +132 -166
- package/dist/es/index.mjs +131 -165
- package/dist/es/mcp-server.mjs +132 -166
- package/dist/lib/cli.js +131 -165
- package/dist/lib/index.js +130 -164
- package/dist/lib/mcp-server.js +131 -165
- package/dist/types/index.d.ts +10 -12
- package/dist/types/mcp-server.d.ts +10 -12
- package/package.json +4 -4
package/dist/es/cli.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { Agent } from "@midscene/core/agent";
|
|
|
9
9
|
import { mergeAndNormalizeAppNameMapping, normalizeForComparison, repeat } from "@midscene/shared/utils";
|
|
10
10
|
import node_assert from "node:assert";
|
|
11
11
|
import { execFile } from "node:child_process";
|
|
12
|
-
import {
|
|
12
|
+
import { createDefaultMobileActions, defineAction } from "@midscene/core/device";
|
|
13
13
|
import { getTmpFile, sleep } from "@midscene/core/utils";
|
|
14
14
|
import { MIDSCENE_ADB_PATH, MIDSCENE_ADB_REMOTE_HOST, MIDSCENE_ADB_REMOTE_PORT, MIDSCENE_ANDROID_IME_STRATEGY, globalConfigManager } from "@midscene/shared/env";
|
|
15
15
|
import { createImgBase64ByFormat, validateScreenshotBuffer } from "@midscene/shared/img";
|
|
@@ -684,103 +684,30 @@ function escapeForShell(text) {
|
|
|
684
684
|
}
|
|
685
685
|
class AndroidDevice {
|
|
686
686
|
actionSpace() {
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
await
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
defineAction({
|
|
699
|
-
name: 'Input',
|
|
700
|
-
description: 'Input text into the input field',
|
|
701
|
-
interfaceAlias: 'aiInput',
|
|
702
|
-
paramSchema: z.object({
|
|
703
|
-
value: 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.'),
|
|
704
|
-
autoDismissKeyboard: 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.'),
|
|
705
|
-
mode: z.preprocess((val)=>'append' === val ? 'typeOnly' : val, z["enum"]([
|
|
706
|
-
'replace',
|
|
707
|
-
'clear',
|
|
708
|
-
'typeOnly'
|
|
709
|
-
]).default('replace').optional().describe('Input mode: "replace" (default) - clear the field and input the value; "typeOnly" - type the value directly without clearing the field first; "clear" - clear the field without inputting new text.')),
|
|
710
|
-
locate: getMidsceneLocationSchema().describe('The input field to be filled').optional()
|
|
711
|
-
}),
|
|
712
|
-
sample: {
|
|
713
|
-
value: 'test@example.com',
|
|
714
|
-
locate: {
|
|
715
|
-
prompt: 'the email input field'
|
|
716
|
-
}
|
|
687
|
+
const mobileActionContext = {
|
|
688
|
+
input: this.inputPrimitives,
|
|
689
|
+
size: ()=>this.size(),
|
|
690
|
+
sleep: async (timeMs)=>{
|
|
691
|
+
await sleep(timeMs);
|
|
692
|
+
},
|
|
693
|
+
getDefaultAutoDismissKeyboard: ()=>this.options?.autoDismissKeyboard,
|
|
694
|
+
systemActions: {
|
|
695
|
+
backButton: {
|
|
696
|
+
name: 'AndroidBackButton',
|
|
697
|
+
description: 'Trigger the system "back" operation on Android devices'
|
|
717
698
|
},
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
autoDismissKeyboard
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
}),
|
|
729
|
-
defineActionScroll(async (param)=>{
|
|
730
|
-
const element = param.locate;
|
|
731
|
-
const startingPoint = element ? {
|
|
732
|
-
left: element.center[0],
|
|
733
|
-
top: element.center[1]
|
|
734
|
-
} : void 0;
|
|
735
|
-
const scrollToEventName = param?.scrollType;
|
|
736
|
-
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
737
|
-
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
738
|
-
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
739
|
-
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
740
|
-
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
741
|
-
else {
|
|
742
|
-
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
|
|
743
|
-
else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
|
|
744
|
-
else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
|
|
745
|
-
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
746
|
-
else await this.scrollDown(param?.distance || void 0, startingPoint);
|
|
747
|
-
await sleep(500);
|
|
748
|
-
}
|
|
749
|
-
}),
|
|
750
|
-
defineActionDragAndDrop(async (param)=>{
|
|
751
|
-
const from = param.from;
|
|
752
|
-
const to = param.to;
|
|
753
|
-
node_assert(from, 'missing "from" param for drag and drop');
|
|
754
|
-
node_assert(to, 'missing "to" param for drag and drop');
|
|
755
|
-
await this.mouseDrag({
|
|
756
|
-
x: from.center[0],
|
|
757
|
-
y: from.center[1]
|
|
758
|
-
}, {
|
|
759
|
-
x: to.center[0],
|
|
760
|
-
y: to.center[1]
|
|
761
|
-
});
|
|
762
|
-
}),
|
|
763
|
-
defineActionSwipe(async (param)=>{
|
|
764
|
-
const { startPoint, endPoint, duration, repeatCount } = normalizeMobileSwipeParam(param, await this.size());
|
|
765
|
-
for(let i = 0; i < repeatCount; i++)await this.mouseDrag(startPoint, endPoint, duration);
|
|
766
|
-
}),
|
|
767
|
-
defineActionKeyboardPress(async (param)=>{
|
|
768
|
-
await this.keyboardPress(param.keyName);
|
|
769
|
-
}),
|
|
770
|
-
defineActionCursorMove(async (param)=>{
|
|
771
|
-
const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
|
|
772
|
-
const times = param.times ?? 1;
|
|
773
|
-
for(let i = 0; i < times; i++){
|
|
774
|
-
await this.keyboardPress(arrowKey);
|
|
775
|
-
await sleep(100);
|
|
699
|
+
homeButton: {
|
|
700
|
+
name: 'AndroidHomeButton',
|
|
701
|
+
description: 'Trigger the system "home" operation on Android devices'
|
|
702
|
+
},
|
|
703
|
+
recentAppsButton: {
|
|
704
|
+
name: 'AndroidRecentAppsButton',
|
|
705
|
+
description: 'Trigger the system "recent apps" operation on Android devices'
|
|
776
706
|
}
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
const [x, y] = element.center;
|
|
782
|
-
await this.longPress(x, y, param?.duration);
|
|
783
|
-
}),
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
const defaultActions = [
|
|
710
|
+
...createDefaultMobileActions(mobileActionContext),
|
|
784
711
|
defineAction({
|
|
785
712
|
name: 'PullGesture',
|
|
786
713
|
description: 'Trigger pull down to refresh or pull up actions',
|
|
@@ -810,19 +737,6 @@ class AndroidDevice {
|
|
|
810
737
|
else if ('up' === param.direction) await this.pullUp(startPoint, param.distance, param.duration);
|
|
811
738
|
else throw new Error(`Unknown pull direction: ${param.direction}`);
|
|
812
739
|
}
|
|
813
|
-
}),
|
|
814
|
-
defineActionPinch(async (param)=>{
|
|
815
|
-
const { centerX, centerY, startDistance, endDistance, duration } = normalizePinchParam(param, await this.size());
|
|
816
|
-
const { x: adjCenterX, y: adjCenterY } = await this.adjustCoordinates(centerX, centerY);
|
|
817
|
-
const ratio = 0 !== adjCenterX && 0 !== centerX ? adjCenterX / centerX : 1;
|
|
818
|
-
const adjStartDist = Math.round(startDistance * ratio);
|
|
819
|
-
const adjEndDist = Math.round(endDistance * ratio);
|
|
820
|
-
await this.ensureYadb();
|
|
821
|
-
const adb = await this.getAdb();
|
|
822
|
-
await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -pinch ${adjCenterX} ${adjCenterY} ${adjStartDist} ${adjEndDist} ${duration}`);
|
|
823
|
-
}),
|
|
824
|
-
defineActionClearInput(async (param)=>{
|
|
825
|
-
await this.clearInput(param.locate);
|
|
826
740
|
})
|
|
827
741
|
];
|
|
828
742
|
const platformSpecificActions = Object.values(createPlatformActions(this));
|
|
@@ -833,6 +747,27 @@ class AndroidDevice {
|
|
|
833
747
|
...customActions
|
|
834
748
|
];
|
|
835
749
|
}
|
|
750
|
+
async performActionScroll(param) {
|
|
751
|
+
const element = param.locate;
|
|
752
|
+
const startingPoint = element ? {
|
|
753
|
+
left: element.center[0],
|
|
754
|
+
top: element.center[1]
|
|
755
|
+
} : void 0;
|
|
756
|
+
const scrollToEventName = param?.scrollType;
|
|
757
|
+
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
758
|
+
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
759
|
+
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
760
|
+
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
761
|
+
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
762
|
+
else {
|
|
763
|
+
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
|
|
764
|
+
else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
|
|
765
|
+
else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
|
|
766
|
+
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
767
|
+
else await this.scrollDown(param?.distance || void 0, startingPoint);
|
|
768
|
+
await sleep(500);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
836
771
|
describe() {
|
|
837
772
|
return this.description || `DeviceId: ${this.deviceId}`;
|
|
838
773
|
}
|
|
@@ -1320,14 +1255,20 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1320
1255
|
return result;
|
|
1321
1256
|
}
|
|
1322
1257
|
async clearInput(element) {
|
|
1323
|
-
if (element) await this.
|
|
1258
|
+
if (element) await this.tapPoint({
|
|
1259
|
+
x: element.center[0],
|
|
1260
|
+
y: element.center[1]
|
|
1261
|
+
});
|
|
1324
1262
|
await this.ensureYadb();
|
|
1325
1263
|
const adb = await this.getAdb();
|
|
1326
1264
|
const IME_STRATEGY = (this.options?.imeStrategy || globalConfigManager.getEnvConfigValue(MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
1327
1265
|
if (IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII) await adb.clearTextField(100);
|
|
1328
1266
|
else await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboardClear`);
|
|
1329
1267
|
if (await adb.isSoftKeyboardPresent()) return;
|
|
1330
|
-
if (element) await this.
|
|
1268
|
+
if (element) await this.tapPoint({
|
|
1269
|
+
x: element.center[0],
|
|
1270
|
+
y: element.center[1]
|
|
1271
|
+
});
|
|
1331
1272
|
}
|
|
1332
1273
|
async forceScreenshot(path) {
|
|
1333
1274
|
await this.ensureYadb();
|
|
@@ -1348,7 +1289,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1348
1289
|
x: start.x,
|
|
1349
1290
|
y: Math.round(height)
|
|
1350
1291
|
};
|
|
1351
|
-
await repeat(defaultScrollUntilTimes, ()=>this.
|
|
1292
|
+
await repeat(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
|
|
1352
1293
|
await sleep(1000);
|
|
1353
1294
|
return;
|
|
1354
1295
|
}
|
|
@@ -1365,7 +1306,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1365
1306
|
x: start.x,
|
|
1366
1307
|
y: 0
|
|
1367
1308
|
};
|
|
1368
|
-
await repeat(defaultScrollUntilTimes, ()=>this.
|
|
1309
|
+
await repeat(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
|
|
1369
1310
|
await sleep(1000);
|
|
1370
1311
|
return;
|
|
1371
1312
|
}
|
|
@@ -1383,7 +1324,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1383
1324
|
x: Math.round(width),
|
|
1384
1325
|
y: start.y
|
|
1385
1326
|
};
|
|
1386
|
-
await repeat(defaultScrollUntilTimes, ()=>this.
|
|
1327
|
+
await repeat(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
|
|
1387
1328
|
await sleep(1000);
|
|
1388
1329
|
return;
|
|
1389
1330
|
}
|
|
@@ -1400,7 +1341,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1400
1341
|
x: 0,
|
|
1401
1342
|
y: start.y
|
|
1402
1343
|
};
|
|
1403
|
-
await repeat(defaultScrollUntilTimes, ()=>this.
|
|
1344
|
+
await repeat(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
|
|
1404
1345
|
await sleep(1000);
|
|
1405
1346
|
return;
|
|
1406
1347
|
}
|
|
@@ -1418,7 +1359,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1418
1359
|
};
|
|
1419
1360
|
const end = this.calculateScrollEndPoint(start, 0, scrollDistance, 0, height);
|
|
1420
1361
|
if (hasExplicitDistance) this.warnScrollDistanceClamped('up', scrollDistance, Math.abs(end.y - start.y));
|
|
1421
|
-
await this.
|
|
1362
|
+
await this.dragPoint(start, end);
|
|
1422
1363
|
return;
|
|
1423
1364
|
}
|
|
1424
1365
|
await this.scroll(0, -scrollDistance, void 0, hasExplicitDistance, 'up');
|
|
@@ -1434,7 +1375,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1434
1375
|
};
|
|
1435
1376
|
const end = this.calculateScrollEndPoint(start, 0, -scrollDistance, 0, height);
|
|
1436
1377
|
if (hasExplicitDistance) this.warnScrollDistanceClamped('down', scrollDistance, Math.abs(end.y - start.y));
|
|
1437
|
-
await this.
|
|
1378
|
+
await this.dragPoint(start, end);
|
|
1438
1379
|
return;
|
|
1439
1380
|
}
|
|
1440
1381
|
await this.scroll(0, scrollDistance, void 0, hasExplicitDistance, 'down');
|
|
@@ -1450,7 +1391,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1450
1391
|
};
|
|
1451
1392
|
const end = this.calculateScrollEndPoint(start, scrollDistance, 0, width, 0);
|
|
1452
1393
|
if (hasExplicitDistance) this.warnScrollDistanceClamped('left', scrollDistance, Math.abs(end.x - start.x));
|
|
1453
|
-
await this.
|
|
1394
|
+
await this.dragPoint(start, end);
|
|
1454
1395
|
return;
|
|
1455
1396
|
}
|
|
1456
1397
|
await this.scroll(-scrollDistance, 0, void 0, hasExplicitDistance, 'left');
|
|
@@ -1466,7 +1407,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1466
1407
|
};
|
|
1467
1408
|
const end = this.calculateScrollEndPoint(start, -scrollDistance, 0, width, 0);
|
|
1468
1409
|
if (hasExplicitDistance) this.warnScrollDistanceClamped('right', scrollDistance, Math.abs(end.x - start.x));
|
|
1469
|
-
await this.
|
|
1410
|
+
await this.dragPoint(start, end);
|
|
1470
1411
|
return;
|
|
1471
1412
|
}
|
|
1472
1413
|
await this.scroll(scrollDistance, 0, void 0, hasExplicitDistance, 'right');
|
|
@@ -1487,7 +1428,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1487
1428
|
const hasBothQuotes = text.includes('"') && text.includes("'");
|
|
1488
1429
|
return hasNonAscii || hasFormatSpecifiers || hasShellSpecialChars || hasBothQuotes;
|
|
1489
1430
|
}
|
|
1490
|
-
async
|
|
1431
|
+
async typeText(text, options) {
|
|
1491
1432
|
if (!text) return;
|
|
1492
1433
|
const adb = await this.getAdb();
|
|
1493
1434
|
const IME_STRATEGY = (this.options?.imeStrategy || globalConfigManager.getEnvConfigValue(MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
@@ -1524,7 +1465,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1524
1465
|
const lowerKey = key.toLowerCase();
|
|
1525
1466
|
return keyMap[lowerKey] || key;
|
|
1526
1467
|
}
|
|
1527
|
-
async
|
|
1468
|
+
async pressKey(key) {
|
|
1528
1469
|
const keyCodeMap = {
|
|
1529
1470
|
Enter: 66,
|
|
1530
1471
|
Backspace: 67,
|
|
@@ -1546,14 +1487,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1546
1487
|
if (asciiCode >= 65 && asciiCode <= 90) await adb.keyevent(asciiCode - 36);
|
|
1547
1488
|
}
|
|
1548
1489
|
}
|
|
1549
|
-
async
|
|
1490
|
+
async tapPoint(point) {
|
|
1550
1491
|
const adb = await this.getAdb();
|
|
1551
|
-
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1492
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(point.x, point.y);
|
|
1552
1493
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} 150`);
|
|
1553
1494
|
}
|
|
1554
|
-
async
|
|
1495
|
+
async doubleTapPoint(point) {
|
|
1555
1496
|
const adb = await this.getAdb();
|
|
1556
|
-
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1497
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(point.x, point.y);
|
|
1557
1498
|
const tapCommand = `input${this.getDisplayArg()} tap ${adjustedX} ${adjustedY}`;
|
|
1558
1499
|
await adb.shell(tapCommand);
|
|
1559
1500
|
await sleep(50);
|
|
@@ -1562,12 +1503,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1562
1503
|
async mouseMove() {
|
|
1563
1504
|
return Promise.resolve();
|
|
1564
1505
|
}
|
|
1565
|
-
async
|
|
1506
|
+
async dragPoint(from, to, duration) {
|
|
1507
|
+
await this.swipePoint(from, to, duration ?? defaultNormalScrollDuration);
|
|
1508
|
+
}
|
|
1509
|
+
async swipePoint(from, to, duration) {
|
|
1566
1510
|
const adb = await this.getAdb();
|
|
1567
1511
|
const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
|
|
1568
1512
|
const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
|
|
1569
|
-
|
|
1570
|
-
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
|
|
1513
|
+
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
|
|
1571
1514
|
}
|
|
1572
1515
|
async scroll(deltaX, deltaY, duration, warnOnClamp = false, direction) {
|
|
1573
1516
|
if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
|
|
@@ -1590,11 +1533,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1590
1533
|
}
|
|
1591
1534
|
const endX = Math.round(startX - deltaX);
|
|
1592
1535
|
const endY = Math.round(startY - deltaY);
|
|
1593
|
-
const { x: adjustedStartX, y: adjustedStartY } = await this.adjustCoordinates(startX, startY);
|
|
1594
|
-
const { x: adjustedEndX, y: adjustedEndY } = await this.adjustCoordinates(endX, endY);
|
|
1595
|
-
const adb = await this.getAdb();
|
|
1596
1536
|
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
1597
|
-
await
|
|
1537
|
+
await this.swipePoint({
|
|
1538
|
+
x: startX,
|
|
1539
|
+
y: startY
|
|
1540
|
+
}, {
|
|
1541
|
+
x: endX,
|
|
1542
|
+
y: endY
|
|
1543
|
+
}, swipeDuration);
|
|
1598
1544
|
}
|
|
1599
1545
|
async destroy() {
|
|
1600
1546
|
if (this.destroyed) return;
|
|
@@ -1642,9 +1588,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1642
1588
|
const adb = await this.getAdb();
|
|
1643
1589
|
await adb.shell(`input${this.getDisplayArg()} keyevent 187`);
|
|
1644
1590
|
}
|
|
1645
|
-
async
|
|
1591
|
+
async longPressPoint(point, duration = 2000) {
|
|
1646
1592
|
const adb = await this.getAdb();
|
|
1647
|
-
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1593
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(point.x, point.y);
|
|
1648
1594
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} ${duration}`);
|
|
1649
1595
|
}
|
|
1650
1596
|
async pullDown(startPoint, distance, duration = 800) {
|
|
@@ -1665,10 +1611,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1665
1611
|
await sleep(200);
|
|
1666
1612
|
}
|
|
1667
1613
|
async pullDrag(from, to, duration) {
|
|
1668
|
-
|
|
1669
|
-
const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
|
|
1670
|
-
const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
|
|
1671
|
-
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
|
|
1614
|
+
await this.swipePoint(from, to, duration);
|
|
1672
1615
|
}
|
|
1673
1616
|
async pullUp(startPoint, distance, duration = 600) {
|
|
1674
1617
|
const { width, height } = await this.size();
|
|
@@ -1769,6 +1712,56 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1769
1712
|
device_define_property(this, "interfaceType", 'android');
|
|
1770
1713
|
device_define_property(this, "uri", void 0);
|
|
1771
1714
|
device_define_property(this, "options", void 0);
|
|
1715
|
+
device_define_property(this, "inputPrimitives", {
|
|
1716
|
+
pointer: {
|
|
1717
|
+
tap: (point)=>this.tapPoint(point),
|
|
1718
|
+
doubleClick: (point)=>this.doubleTapPoint(point),
|
|
1719
|
+
longPress: (point, opts)=>this.longPressPoint(point, opts?.duration),
|
|
1720
|
+
dragAndDrop: (from, to)=>this.dragPoint(from, to)
|
|
1721
|
+
},
|
|
1722
|
+
keyboard: {
|
|
1723
|
+
keyboardPress: (keyName)=>this.pressKey(keyName),
|
|
1724
|
+
typeText: async (value, opts)=>{
|
|
1725
|
+
const target = opts?.target;
|
|
1726
|
+
if (target && opts?.replace !== false) await this.clearInput(target);
|
|
1727
|
+
else if (target) await this.tapPoint({
|
|
1728
|
+
x: target.center[0],
|
|
1729
|
+
y: target.center[1]
|
|
1730
|
+
});
|
|
1731
|
+
if (opts?.focusOnly) return;
|
|
1732
|
+
await this.typeText(value, opts);
|
|
1733
|
+
},
|
|
1734
|
+
clearInput: (target)=>this.clearInput(target),
|
|
1735
|
+
cursorMove: async (direction, times = 1)=>{
|
|
1736
|
+
const arrowKey = 'left' === direction ? 'ArrowLeft' : 'ArrowRight';
|
|
1737
|
+
for(let i = 0; i < times; i++)await this.pressKey(arrowKey);
|
|
1738
|
+
}
|
|
1739
|
+
},
|
|
1740
|
+
touch: {
|
|
1741
|
+
swipe: async (start, end, opts)=>{
|
|
1742
|
+
const duration = opts?.duration ?? 300;
|
|
1743
|
+
const repeatCount = opts?.repeat ?? 1;
|
|
1744
|
+
for(let i = 0; i < repeatCount; i++)await this.dragPoint(start, end, duration);
|
|
1745
|
+
},
|
|
1746
|
+
pinch: async (center, opts)=>{
|
|
1747
|
+
const { x: adjCenterX, y: adjCenterY } = await this.adjustCoordinates(Math.round(center.x), Math.round(center.y));
|
|
1748
|
+
const ratio = 0 !== adjCenterX && 0 !== center.x ? adjCenterX / center.x : 1;
|
|
1749
|
+
const adjStartDist = Math.round(opts.startDistance * ratio);
|
|
1750
|
+
const adjEndDist = Math.round(opts.endDistance * ratio);
|
|
1751
|
+
await this.ensureYadb();
|
|
1752
|
+
const adb = await this.getAdb();
|
|
1753
|
+
await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -pinch ${adjCenterX} ${adjCenterY} ${adjStartDist} ${adjEndDist} ${opts.duration}`);
|
|
1754
|
+
}
|
|
1755
|
+
},
|
|
1756
|
+
scroll: {
|
|
1757
|
+
scroll: (param)=>this.performActionScroll(param)
|
|
1758
|
+
},
|
|
1759
|
+
system: {
|
|
1760
|
+
backButton: ()=>this.back(),
|
|
1761
|
+
homeButton: ()=>this.home(),
|
|
1762
|
+
recentAppsButton: ()=>this.recentApps()
|
|
1763
|
+
}
|
|
1764
|
+
});
|
|
1772
1765
|
node_assert(deviceId, 'deviceId is required for AndroidDevice');
|
|
1773
1766
|
this.deviceId = deviceId;
|
|
1774
1767
|
this.options = options;
|
|
@@ -1819,37 +1812,10 @@ const createPlatformActions = (device)=>({
|
|
|
1819
1812
|
description: 'Terminate (force-stop) an Android app by package name',
|
|
1820
1813
|
interfaceAlias: 'terminate',
|
|
1821
1814
|
paramSchema: terminateParamSchema,
|
|
1822
|
-
delayBeforeRunner: 0,
|
|
1823
|
-
delayAfterRunner: 0,
|
|
1824
1815
|
call: async (param)=>{
|
|
1825
1816
|
if (!param.uri || '' === param.uri.trim()) throw new Error('Terminate requires a non-empty uri parameter');
|
|
1826
1817
|
await device.terminate(param.uri);
|
|
1827
1818
|
}
|
|
1828
|
-
}),
|
|
1829
|
-
AndroidBackButton: defineAction({
|
|
1830
|
-
name: 'AndroidBackButton',
|
|
1831
|
-
description: 'Trigger the system "back" operation on Android devices',
|
|
1832
|
-
delayBeforeRunner: 0,
|
|
1833
|
-
delayAfterRunner: 0,
|
|
1834
|
-
call: async ()=>{
|
|
1835
|
-
await device.back();
|
|
1836
|
-
}
|
|
1837
|
-
}),
|
|
1838
|
-
AndroidHomeButton: defineAction({
|
|
1839
|
-
name: 'AndroidHomeButton',
|
|
1840
|
-
description: 'Trigger the system "home" operation on Android devices',
|
|
1841
|
-
delayBeforeRunner: 0,
|
|
1842
|
-
delayAfterRunner: 0,
|
|
1843
|
-
call: async ()=>{
|
|
1844
|
-
await device.home();
|
|
1845
|
-
}
|
|
1846
|
-
}),
|
|
1847
|
-
AndroidRecentAppsButton: defineAction({
|
|
1848
|
-
name: 'AndroidRecentAppsButton',
|
|
1849
|
-
description: 'Trigger the system "recent apps" operation on Android devices',
|
|
1850
|
-
call: async ()=>{
|
|
1851
|
-
await device.recentApps();
|
|
1852
|
-
}
|
|
1853
1819
|
})
|
|
1854
1820
|
});
|
|
1855
1821
|
const debugUtils = (0, logger_.getDebug)('android:utils');
|
|
@@ -2016,7 +1982,7 @@ class AndroidMidsceneTools extends BaseMidsceneTools {
|
|
|
2016
1982
|
const tools = new AndroidMidsceneTools();
|
|
2017
1983
|
runToolsCLI(tools, 'midscene-android', {
|
|
2018
1984
|
stripPrefix: 'android_',
|
|
2019
|
-
version: "1.8.
|
|
1985
|
+
version: "1.8.1",
|
|
2020
1986
|
extraCommands: createReportCliCommands()
|
|
2021
1987
|
}).catch((e)=>{
|
|
2022
1988
|
process.exit(reportCLIError(e));
|