@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/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import * as __rspack_external_node_path_c5b9b54f from "node:path";
|
|
|
5
5
|
import node_assert from "node:assert";
|
|
6
6
|
import { execFile } from "node:child_process";
|
|
7
7
|
import { getMidsceneLocationSchema, z } from "@midscene/core";
|
|
8
|
-
import {
|
|
8
|
+
import { createDefaultMobileActions, defineAction } from "@midscene/core/device";
|
|
9
9
|
import { getTmpFile, sleep } from "@midscene/core/utils";
|
|
10
10
|
import { MIDSCENE_ADB_PATH, MIDSCENE_ADB_REMOTE_HOST, MIDSCENE_ADB_REMOTE_PORT, MIDSCENE_ANDROID_IME_STRATEGY, globalConfigManager, overrideAIConfig } from "@midscene/shared/env";
|
|
11
11
|
import { createImgBase64ByFormat, validateScreenshotBuffer } from "@midscene/shared/img";
|
|
@@ -587,103 +587,30 @@ function escapeForShell(text) {
|
|
|
587
587
|
}
|
|
588
588
|
class AndroidDevice {
|
|
589
589
|
actionSpace() {
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
await
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
defineAction({
|
|
602
|
-
name: 'Input',
|
|
603
|
-
description: 'Input text into the input field',
|
|
604
|
-
interfaceAlias: 'aiInput',
|
|
605
|
-
paramSchema: z.object({
|
|
606
|
-
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.'),
|
|
607
|
-
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.'),
|
|
608
|
-
mode: z.preprocess((val)=>'append' === val ? 'typeOnly' : val, z["enum"]([
|
|
609
|
-
'replace',
|
|
610
|
-
'clear',
|
|
611
|
-
'typeOnly'
|
|
612
|
-
]).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.')),
|
|
613
|
-
locate: getMidsceneLocationSchema().describe('The input field to be filled').optional()
|
|
614
|
-
}),
|
|
615
|
-
sample: {
|
|
616
|
-
value: 'test@example.com',
|
|
617
|
-
locate: {
|
|
618
|
-
prompt: 'the email input field'
|
|
619
|
-
}
|
|
590
|
+
const mobileActionContext = {
|
|
591
|
+
input: this.inputPrimitives,
|
|
592
|
+
size: ()=>this.size(),
|
|
593
|
+
sleep: async (timeMs)=>{
|
|
594
|
+
await sleep(timeMs);
|
|
595
|
+
},
|
|
596
|
+
getDefaultAutoDismissKeyboard: ()=>this.options?.autoDismissKeyboard,
|
|
597
|
+
systemActions: {
|
|
598
|
+
backButton: {
|
|
599
|
+
name: 'AndroidBackButton',
|
|
600
|
+
description: 'Trigger the system "back" operation on Android devices'
|
|
620
601
|
},
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
autoDismissKeyboard
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
}),
|
|
632
|
-
defineActionScroll(async (param)=>{
|
|
633
|
-
const element = param.locate;
|
|
634
|
-
const startingPoint = element ? {
|
|
635
|
-
left: element.center[0],
|
|
636
|
-
top: element.center[1]
|
|
637
|
-
} : void 0;
|
|
638
|
-
const scrollToEventName = param?.scrollType;
|
|
639
|
-
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
640
|
-
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
641
|
-
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
642
|
-
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
643
|
-
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
644
|
-
else {
|
|
645
|
-
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
|
|
646
|
-
else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
|
|
647
|
-
else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
|
|
648
|
-
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
649
|
-
else await this.scrollDown(param?.distance || void 0, startingPoint);
|
|
650
|
-
await sleep(500);
|
|
651
|
-
}
|
|
652
|
-
}),
|
|
653
|
-
defineActionDragAndDrop(async (param)=>{
|
|
654
|
-
const from = param.from;
|
|
655
|
-
const to = param.to;
|
|
656
|
-
node_assert(from, 'missing "from" param for drag and drop');
|
|
657
|
-
node_assert(to, 'missing "to" param for drag and drop');
|
|
658
|
-
await this.mouseDrag({
|
|
659
|
-
x: from.center[0],
|
|
660
|
-
y: from.center[1]
|
|
661
|
-
}, {
|
|
662
|
-
x: to.center[0],
|
|
663
|
-
y: to.center[1]
|
|
664
|
-
});
|
|
665
|
-
}),
|
|
666
|
-
defineActionSwipe(async (param)=>{
|
|
667
|
-
const { startPoint, endPoint, duration, repeatCount } = normalizeMobileSwipeParam(param, await this.size());
|
|
668
|
-
for(let i = 0; i < repeatCount; i++)await this.mouseDrag(startPoint, endPoint, duration);
|
|
669
|
-
}),
|
|
670
|
-
defineActionKeyboardPress(async (param)=>{
|
|
671
|
-
await this.keyboardPress(param.keyName);
|
|
672
|
-
}),
|
|
673
|
-
defineActionCursorMove(async (param)=>{
|
|
674
|
-
const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
|
|
675
|
-
const times = param.times ?? 1;
|
|
676
|
-
for(let i = 0; i < times; i++){
|
|
677
|
-
await this.keyboardPress(arrowKey);
|
|
678
|
-
await sleep(100);
|
|
602
|
+
homeButton: {
|
|
603
|
+
name: 'AndroidHomeButton',
|
|
604
|
+
description: 'Trigger the system "home" operation on Android devices'
|
|
605
|
+
},
|
|
606
|
+
recentAppsButton: {
|
|
607
|
+
name: 'AndroidRecentAppsButton',
|
|
608
|
+
description: 'Trigger the system "recent apps" operation on Android devices'
|
|
679
609
|
}
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
const [x, y] = element.center;
|
|
685
|
-
await this.longPress(x, y, param?.duration);
|
|
686
|
-
}),
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
const defaultActions = [
|
|
613
|
+
...createDefaultMobileActions(mobileActionContext),
|
|
687
614
|
defineAction({
|
|
688
615
|
name: 'PullGesture',
|
|
689
616
|
description: 'Trigger pull down to refresh or pull up actions',
|
|
@@ -713,19 +640,6 @@ class AndroidDevice {
|
|
|
713
640
|
else if ('up' === param.direction) await this.pullUp(startPoint, param.distance, param.duration);
|
|
714
641
|
else throw new Error(`Unknown pull direction: ${param.direction}`);
|
|
715
642
|
}
|
|
716
|
-
}),
|
|
717
|
-
defineActionPinch(async (param)=>{
|
|
718
|
-
const { centerX, centerY, startDistance, endDistance, duration } = normalizePinchParam(param, await this.size());
|
|
719
|
-
const { x: adjCenterX, y: adjCenterY } = await this.adjustCoordinates(centerX, centerY);
|
|
720
|
-
const ratio = 0 !== adjCenterX && 0 !== centerX ? adjCenterX / centerX : 1;
|
|
721
|
-
const adjStartDist = Math.round(startDistance * ratio);
|
|
722
|
-
const adjEndDist = Math.round(endDistance * ratio);
|
|
723
|
-
await this.ensureYadb();
|
|
724
|
-
const adb = await this.getAdb();
|
|
725
|
-
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}`);
|
|
726
|
-
}),
|
|
727
|
-
defineActionClearInput(async (param)=>{
|
|
728
|
-
await this.clearInput(param.locate);
|
|
729
643
|
})
|
|
730
644
|
];
|
|
731
645
|
const platformSpecificActions = Object.values(createPlatformActions(this));
|
|
@@ -736,6 +650,27 @@ class AndroidDevice {
|
|
|
736
650
|
...customActions
|
|
737
651
|
];
|
|
738
652
|
}
|
|
653
|
+
async performActionScroll(param) {
|
|
654
|
+
const element = param.locate;
|
|
655
|
+
const startingPoint = element ? {
|
|
656
|
+
left: element.center[0],
|
|
657
|
+
top: element.center[1]
|
|
658
|
+
} : void 0;
|
|
659
|
+
const scrollToEventName = param?.scrollType;
|
|
660
|
+
if ('scrollToTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
|
|
661
|
+
else if ('scrollToBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
|
|
662
|
+
else if ('scrollToRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
|
|
663
|
+
else if ('scrollToLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
|
|
664
|
+
else if ('singleAction' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
|
|
665
|
+
else {
|
|
666
|
+
if (param?.direction !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
|
|
667
|
+
else if ('left' === param.direction) await this.scrollLeft(param.distance || void 0, startingPoint);
|
|
668
|
+
else if ('right' === param.direction) await this.scrollRight(param.distance || void 0, startingPoint);
|
|
669
|
+
else throw new Error(`Unknown scroll direction: ${param.direction}`);
|
|
670
|
+
else await this.scrollDown(param?.distance || void 0, startingPoint);
|
|
671
|
+
await sleep(500);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
739
674
|
describe() {
|
|
740
675
|
return this.description || `DeviceId: ${this.deviceId}`;
|
|
741
676
|
}
|
|
@@ -1223,14 +1158,20 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1223
1158
|
return result;
|
|
1224
1159
|
}
|
|
1225
1160
|
async clearInput(element) {
|
|
1226
|
-
if (element) await this.
|
|
1161
|
+
if (element) await this.tapPoint({
|
|
1162
|
+
x: element.center[0],
|
|
1163
|
+
y: element.center[1]
|
|
1164
|
+
});
|
|
1227
1165
|
await this.ensureYadb();
|
|
1228
1166
|
const adb = await this.getAdb();
|
|
1229
1167
|
const IME_STRATEGY = (this.options?.imeStrategy || globalConfigManager.getEnvConfigValue(MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
1230
1168
|
if (IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII) await adb.clearTextField(100);
|
|
1231
1169
|
else await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboardClear`);
|
|
1232
1170
|
if (await adb.isSoftKeyboardPresent()) return;
|
|
1233
|
-
if (element) await this.
|
|
1171
|
+
if (element) await this.tapPoint({
|
|
1172
|
+
x: element.center[0],
|
|
1173
|
+
y: element.center[1]
|
|
1174
|
+
});
|
|
1234
1175
|
}
|
|
1235
1176
|
async forceScreenshot(path) {
|
|
1236
1177
|
await this.ensureYadb();
|
|
@@ -1251,7 +1192,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1251
1192
|
x: start.x,
|
|
1252
1193
|
y: Math.round(height)
|
|
1253
1194
|
};
|
|
1254
|
-
await repeat(defaultScrollUntilTimes, ()=>this.
|
|
1195
|
+
await repeat(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
|
|
1255
1196
|
await sleep(1000);
|
|
1256
1197
|
return;
|
|
1257
1198
|
}
|
|
@@ -1268,7 +1209,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1268
1209
|
x: start.x,
|
|
1269
1210
|
y: 0
|
|
1270
1211
|
};
|
|
1271
|
-
await repeat(defaultScrollUntilTimes, ()=>this.
|
|
1212
|
+
await repeat(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
|
|
1272
1213
|
await sleep(1000);
|
|
1273
1214
|
return;
|
|
1274
1215
|
}
|
|
@@ -1286,7 +1227,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1286
1227
|
x: Math.round(width),
|
|
1287
1228
|
y: start.y
|
|
1288
1229
|
};
|
|
1289
|
-
await repeat(defaultScrollUntilTimes, ()=>this.
|
|
1230
|
+
await repeat(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
|
|
1290
1231
|
await sleep(1000);
|
|
1291
1232
|
return;
|
|
1292
1233
|
}
|
|
@@ -1303,7 +1244,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1303
1244
|
x: 0,
|
|
1304
1245
|
y: start.y
|
|
1305
1246
|
};
|
|
1306
|
-
await repeat(defaultScrollUntilTimes, ()=>this.
|
|
1247
|
+
await repeat(defaultScrollUntilTimes, ()=>this.dragPoint(start, end, defaultFastScrollDuration));
|
|
1307
1248
|
await sleep(1000);
|
|
1308
1249
|
return;
|
|
1309
1250
|
}
|
|
@@ -1321,7 +1262,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1321
1262
|
};
|
|
1322
1263
|
const end = this.calculateScrollEndPoint(start, 0, scrollDistance, 0, height);
|
|
1323
1264
|
if (hasExplicitDistance) this.warnScrollDistanceClamped('up', scrollDistance, Math.abs(end.y - start.y));
|
|
1324
|
-
await this.
|
|
1265
|
+
await this.dragPoint(start, end);
|
|
1325
1266
|
return;
|
|
1326
1267
|
}
|
|
1327
1268
|
await this.scroll(0, -scrollDistance, void 0, hasExplicitDistance, 'up');
|
|
@@ -1337,7 +1278,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1337
1278
|
};
|
|
1338
1279
|
const end = this.calculateScrollEndPoint(start, 0, -scrollDistance, 0, height);
|
|
1339
1280
|
if (hasExplicitDistance) this.warnScrollDistanceClamped('down', scrollDistance, Math.abs(end.y - start.y));
|
|
1340
|
-
await this.
|
|
1281
|
+
await this.dragPoint(start, end);
|
|
1341
1282
|
return;
|
|
1342
1283
|
}
|
|
1343
1284
|
await this.scroll(0, scrollDistance, void 0, hasExplicitDistance, 'down');
|
|
@@ -1353,7 +1294,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1353
1294
|
};
|
|
1354
1295
|
const end = this.calculateScrollEndPoint(start, scrollDistance, 0, width, 0);
|
|
1355
1296
|
if (hasExplicitDistance) this.warnScrollDistanceClamped('left', scrollDistance, Math.abs(end.x - start.x));
|
|
1356
|
-
await this.
|
|
1297
|
+
await this.dragPoint(start, end);
|
|
1357
1298
|
return;
|
|
1358
1299
|
}
|
|
1359
1300
|
await this.scroll(-scrollDistance, 0, void 0, hasExplicitDistance, 'left');
|
|
@@ -1369,7 +1310,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1369
1310
|
};
|
|
1370
1311
|
const end = this.calculateScrollEndPoint(start, -scrollDistance, 0, width, 0);
|
|
1371
1312
|
if (hasExplicitDistance) this.warnScrollDistanceClamped('right', scrollDistance, Math.abs(end.x - start.x));
|
|
1372
|
-
await this.
|
|
1313
|
+
await this.dragPoint(start, end);
|
|
1373
1314
|
return;
|
|
1374
1315
|
}
|
|
1375
1316
|
await this.scroll(scrollDistance, 0, void 0, hasExplicitDistance, 'right');
|
|
@@ -1390,7 +1331,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1390
1331
|
const hasBothQuotes = text.includes('"') && text.includes("'");
|
|
1391
1332
|
return hasNonAscii || hasFormatSpecifiers || hasShellSpecialChars || hasBothQuotes;
|
|
1392
1333
|
}
|
|
1393
|
-
async
|
|
1334
|
+
async typeText(text, options) {
|
|
1394
1335
|
if (!text) return;
|
|
1395
1336
|
const adb = await this.getAdb();
|
|
1396
1337
|
const IME_STRATEGY = (this.options?.imeStrategy || globalConfigManager.getEnvConfigValue(MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
@@ -1427,7 +1368,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1427
1368
|
const lowerKey = key.toLowerCase();
|
|
1428
1369
|
return keyMap[lowerKey] || key;
|
|
1429
1370
|
}
|
|
1430
|
-
async
|
|
1371
|
+
async pressKey(key) {
|
|
1431
1372
|
const keyCodeMap = {
|
|
1432
1373
|
Enter: 66,
|
|
1433
1374
|
Backspace: 67,
|
|
@@ -1449,14 +1390,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1449
1390
|
if (asciiCode >= 65 && asciiCode <= 90) await adb.keyevent(asciiCode - 36);
|
|
1450
1391
|
}
|
|
1451
1392
|
}
|
|
1452
|
-
async
|
|
1393
|
+
async tapPoint(point) {
|
|
1453
1394
|
const adb = await this.getAdb();
|
|
1454
|
-
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1395
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(point.x, point.y);
|
|
1455
1396
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} 150`);
|
|
1456
1397
|
}
|
|
1457
|
-
async
|
|
1398
|
+
async doubleTapPoint(point) {
|
|
1458
1399
|
const adb = await this.getAdb();
|
|
1459
|
-
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1400
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(point.x, point.y);
|
|
1460
1401
|
const tapCommand = `input${this.getDisplayArg()} tap ${adjustedX} ${adjustedY}`;
|
|
1461
1402
|
await adb.shell(tapCommand);
|
|
1462
1403
|
await sleep(50);
|
|
@@ -1465,12 +1406,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1465
1406
|
async mouseMove() {
|
|
1466
1407
|
return Promise.resolve();
|
|
1467
1408
|
}
|
|
1468
|
-
async
|
|
1409
|
+
async dragPoint(from, to, duration) {
|
|
1410
|
+
await this.swipePoint(from, to, duration ?? defaultNormalScrollDuration);
|
|
1411
|
+
}
|
|
1412
|
+
async swipePoint(from, to, duration) {
|
|
1469
1413
|
const adb = await this.getAdb();
|
|
1470
1414
|
const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
|
|
1471
1415
|
const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
|
|
1472
|
-
|
|
1473
|
-
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
|
|
1416
|
+
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
|
|
1474
1417
|
}
|
|
1475
1418
|
async scroll(deltaX, deltaY, duration, warnOnClamp = false, direction) {
|
|
1476
1419
|
if (0 === deltaX && 0 === deltaY) throw new Error('Scroll distance cannot be zero in both directions');
|
|
@@ -1493,11 +1436,14 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1493
1436
|
}
|
|
1494
1437
|
const endX = Math.round(startX - deltaX);
|
|
1495
1438
|
const endY = Math.round(startY - deltaY);
|
|
1496
|
-
const { x: adjustedStartX, y: adjustedStartY } = await this.adjustCoordinates(startX, startY);
|
|
1497
|
-
const { x: adjustedEndX, y: adjustedEndY } = await this.adjustCoordinates(endX, endY);
|
|
1498
|
-
const adb = await this.getAdb();
|
|
1499
1439
|
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
1500
|
-
await
|
|
1440
|
+
await this.swipePoint({
|
|
1441
|
+
x: startX,
|
|
1442
|
+
y: startY
|
|
1443
|
+
}, {
|
|
1444
|
+
x: endX,
|
|
1445
|
+
y: endY
|
|
1446
|
+
}, swipeDuration);
|
|
1501
1447
|
}
|
|
1502
1448
|
async destroy() {
|
|
1503
1449
|
if (this.destroyed) return;
|
|
@@ -1545,9 +1491,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1545
1491
|
const adb = await this.getAdb();
|
|
1546
1492
|
await adb.shell(`input${this.getDisplayArg()} keyevent 187`);
|
|
1547
1493
|
}
|
|
1548
|
-
async
|
|
1494
|
+
async longPressPoint(point, duration = 2000) {
|
|
1549
1495
|
const adb = await this.getAdb();
|
|
1550
|
-
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1496
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(point.x, point.y);
|
|
1551
1497
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} ${duration}`);
|
|
1552
1498
|
}
|
|
1553
1499
|
async pullDown(startPoint, distance, duration = 800) {
|
|
@@ -1568,10 +1514,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1568
1514
|
await sleep(200);
|
|
1569
1515
|
}
|
|
1570
1516
|
async pullDrag(from, to, duration) {
|
|
1571
|
-
|
|
1572
|
-
const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
|
|
1573
|
-
const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
|
|
1574
|
-
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
|
|
1517
|
+
await this.swipePoint(from, to, duration);
|
|
1575
1518
|
}
|
|
1576
1519
|
async pullUp(startPoint, distance, duration = 600) {
|
|
1577
1520
|
const { width, height } = await this.size();
|
|
@@ -1672,6 +1615,56 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1672
1615
|
device_define_property(this, "interfaceType", 'android');
|
|
1673
1616
|
device_define_property(this, "uri", void 0);
|
|
1674
1617
|
device_define_property(this, "options", void 0);
|
|
1618
|
+
device_define_property(this, "inputPrimitives", {
|
|
1619
|
+
pointer: {
|
|
1620
|
+
tap: (point)=>this.tapPoint(point),
|
|
1621
|
+
doubleClick: (point)=>this.doubleTapPoint(point),
|
|
1622
|
+
longPress: (point, opts)=>this.longPressPoint(point, opts?.duration),
|
|
1623
|
+
dragAndDrop: (from, to)=>this.dragPoint(from, to)
|
|
1624
|
+
},
|
|
1625
|
+
keyboard: {
|
|
1626
|
+
keyboardPress: (keyName)=>this.pressKey(keyName),
|
|
1627
|
+
typeText: async (value, opts)=>{
|
|
1628
|
+
const target = opts?.target;
|
|
1629
|
+
if (target && opts?.replace !== false) await this.clearInput(target);
|
|
1630
|
+
else if (target) await this.tapPoint({
|
|
1631
|
+
x: target.center[0],
|
|
1632
|
+
y: target.center[1]
|
|
1633
|
+
});
|
|
1634
|
+
if (opts?.focusOnly) return;
|
|
1635
|
+
await this.typeText(value, opts);
|
|
1636
|
+
},
|
|
1637
|
+
clearInput: (target)=>this.clearInput(target),
|
|
1638
|
+
cursorMove: async (direction, times = 1)=>{
|
|
1639
|
+
const arrowKey = 'left' === direction ? 'ArrowLeft' : 'ArrowRight';
|
|
1640
|
+
for(let i = 0; i < times; i++)await this.pressKey(arrowKey);
|
|
1641
|
+
}
|
|
1642
|
+
},
|
|
1643
|
+
touch: {
|
|
1644
|
+
swipe: async (start, end, opts)=>{
|
|
1645
|
+
const duration = opts?.duration ?? 300;
|
|
1646
|
+
const repeatCount = opts?.repeat ?? 1;
|
|
1647
|
+
for(let i = 0; i < repeatCount; i++)await this.dragPoint(start, end, duration);
|
|
1648
|
+
},
|
|
1649
|
+
pinch: async (center, opts)=>{
|
|
1650
|
+
const { x: adjCenterX, y: adjCenterY } = await this.adjustCoordinates(Math.round(center.x), Math.round(center.y));
|
|
1651
|
+
const ratio = 0 !== adjCenterX && 0 !== center.x ? adjCenterX / center.x : 1;
|
|
1652
|
+
const adjStartDist = Math.round(opts.startDistance * ratio);
|
|
1653
|
+
const adjEndDist = Math.round(opts.endDistance * ratio);
|
|
1654
|
+
await this.ensureYadb();
|
|
1655
|
+
const adb = await this.getAdb();
|
|
1656
|
+
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}`);
|
|
1657
|
+
}
|
|
1658
|
+
},
|
|
1659
|
+
scroll: {
|
|
1660
|
+
scroll: (param)=>this.performActionScroll(param)
|
|
1661
|
+
},
|
|
1662
|
+
system: {
|
|
1663
|
+
backButton: ()=>this.back(),
|
|
1664
|
+
homeButton: ()=>this.home(),
|
|
1665
|
+
recentAppsButton: ()=>this.recentApps()
|
|
1666
|
+
}
|
|
1667
|
+
});
|
|
1675
1668
|
node_assert(deviceId, 'deviceId is required for AndroidDevice');
|
|
1676
1669
|
this.deviceId = deviceId;
|
|
1677
1670
|
this.options = options;
|
|
@@ -1722,37 +1715,10 @@ const createPlatformActions = (device)=>({
|
|
|
1722
1715
|
description: 'Terminate (force-stop) an Android app by package name',
|
|
1723
1716
|
interfaceAlias: 'terminate',
|
|
1724
1717
|
paramSchema: terminateParamSchema,
|
|
1725
|
-
delayBeforeRunner: 0,
|
|
1726
|
-
delayAfterRunner: 0,
|
|
1727
1718
|
call: async (param)=>{
|
|
1728
1719
|
if (!param.uri || '' === param.uri.trim()) throw new Error('Terminate requires a non-empty uri parameter');
|
|
1729
1720
|
await device.terminate(param.uri);
|
|
1730
1721
|
}
|
|
1731
|
-
}),
|
|
1732
|
-
AndroidBackButton: defineAction({
|
|
1733
|
-
name: 'AndroidBackButton',
|
|
1734
|
-
description: 'Trigger the system "back" operation on Android devices',
|
|
1735
|
-
delayBeforeRunner: 0,
|
|
1736
|
-
delayAfterRunner: 0,
|
|
1737
|
-
call: async ()=>{
|
|
1738
|
-
await device.back();
|
|
1739
|
-
}
|
|
1740
|
-
}),
|
|
1741
|
-
AndroidHomeButton: defineAction({
|
|
1742
|
-
name: 'AndroidHomeButton',
|
|
1743
|
-
description: 'Trigger the system "home" operation on Android devices',
|
|
1744
|
-
delayBeforeRunner: 0,
|
|
1745
|
-
delayAfterRunner: 0,
|
|
1746
|
-
call: async ()=>{
|
|
1747
|
-
await device.home();
|
|
1748
|
-
}
|
|
1749
|
-
}),
|
|
1750
|
-
AndroidRecentAppsButton: defineAction({
|
|
1751
|
-
name: 'AndroidRecentAppsButton',
|
|
1752
|
-
description: 'Trigger the system "recent apps" operation on Android devices',
|
|
1753
|
-
call: async ()=>{
|
|
1754
|
-
await device.recentApps();
|
|
1755
|
-
}
|
|
1756
1722
|
})
|
|
1757
1723
|
});
|
|
1758
1724
|
const defaultAppNameMapping = {
|