@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/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 { defineAction, defineActionClearInput, defineActionCursorMove, defineActionDoubleClick, defineActionDragAndDrop, defineActionKeyboardPress, defineActionLongPress, defineActionPinch, defineActionScroll, defineActionSwipe, defineActionTap, normalizeMobileSwipeParam, normalizePinchParam } from "@midscene/core/device";
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 defaultActions = [
591
- defineActionTap(async (param)=>{
592
- const element = param.locate;
593
- node_assert(element, 'Element not found, cannot tap');
594
- await this.mouseClick(element.center[0], element.center[1]);
595
- }),
596
- defineActionDoubleClick(async (param)=>{
597
- const element = param.locate;
598
- node_assert(element, 'Element not found, cannot double click');
599
- await this.mouseDoubleClick(element.center[0], element.center[1]);
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
- call: async (param)=>{
622
- const element = param.locate;
623
- if ('typeOnly' !== param.mode) await this.clearInput(element);
624
- if ('clear' === param.mode) return;
625
- if (!param || !param.value) return;
626
- const autoDismissKeyboard = param.autoDismissKeyboard ?? this.options?.autoDismissKeyboard;
627
- await this.keyboardType(param.value, {
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
- defineActionLongPress(async (param)=>{
682
- const element = param.locate;
683
- if (!element) throw new Error('LongPress requires an element to be located');
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.mouseClick(element.center[0], element.center[1]);
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.mouseClick(element.center[0], element.center[1]);
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.mouseDrag(start, end, defaultFastScrollDuration));
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.mouseDrag(start, end, defaultFastScrollDuration));
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.mouseDrag(start, end, defaultFastScrollDuration));
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.mouseDrag(start, end, defaultFastScrollDuration));
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.mouseDrag(start, end);
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.mouseDrag(start, end);
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.mouseDrag(start, end);
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.mouseDrag(start, end);
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 keyboardType(text, options) {
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 keyboardPress(key) {
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 mouseClick(x, y) {
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 mouseDoubleClick(x, y) {
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 mouseDrag(from, to, duration) {
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
- const swipeDuration = duration ?? defaultNormalScrollDuration;
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 adb.shell(`input${this.getDisplayArg()} swipe ${adjustedStartX} ${adjustedStartY} ${adjustedEndX} ${adjustedEndY} ${swipeDuration}`);
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 longPress(x, y, duration = 2000) {
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
- const adb = await this.getAdb();
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 = {