@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 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 ?? (null == (_this_options = this.options) ? void 0 : _this_options.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 = null == param ? void 0 : param.scrollType;
78
- if ('untilTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
79
- else if ('untilBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
80
- else if ('untilRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
81
- else if ('untilLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
82
- else if ('once' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
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 ((null == param ? void 0 : param.direction) !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
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((null == param ? void 0 : param.distance) || void 0, startingPoint);
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, null == param ? void 0 : param.duration);
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
- var _this_options, _this_options1, _this_options2;
197
- const androidAdbPath = (null == (_this_options = this.options) ? void 0 : _this_options.androidAdbPath) || globalConfigManager.getEnvConfigValue(MIDSCENE_ADB_PATH);
198
- const remoteAdbHost = (null == (_this_options1 = this.options) ? void 0 : _this_options1.remoteAdbHost) || globalConfigManager.getEnvConfigValue(MIDSCENE_ADB_REMOTE_HOST);
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] ? " \u2705" : ''}`).join('\n')}
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
- var _this_options, _this_options1;
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 (null == (_this_options1 = this.options) ? void 0 : _this_options1.displayId)) try {
294
- var _this_options2;
268
+ if ('number' == typeof this.options?.displayId) try {
295
269
  const stdout = await adb.shell('dumpsys display');
296
- if (null == (_this_options2 = this.options) ? void 0 : _this_options2.usePhysicalDisplayIdForDisplayLookup) {
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 (null == (_this_options = this.options) ? void 0 : _this_options.displayId)) try {
389
- var _this_options1;
364
+ if ('number' == typeof this.options?.displayId) try {
390
365
  const stdout = await adb.shell('dumpsys display');
391
- if (null == (_this_options1 = this.options) ? void 0 : _this_options1.usePhysicalDisplayIdForDisplayLookup) {
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
- var _this_options;
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 width = Number.parseInt(match[isLandscape ? 2 : 1], 10);
458
- const height = Number.parseInt(match[isLandscape ? 1 : 2], 10);
459
- const scale = (null == (_this_options = this.options) ? void 0 : _this_options.screenshotResizeScale) ?? 1 / this.devicePixelRatio;
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 (null == (_this_options = this.options) ? void 0 : _this_options.displayId);
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 = (null == (_this_options2 = this.options) ? void 0 : _this_options2.usePhysicalDisplayIdForScreenshot) ? await this.getPhysicalDisplayId() : null == (_this_options3 = this.options) ? void 0 : _this_options3.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 = ((null == (_this_options = this.options) ? void 0 : _this_options.imeStrategy) || globalConfigManager.getEnvConfigValue(MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
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 = ((null == (_this_options = this.options) ? void 0 : _this_options.imeStrategy) || globalConfigManager.getEnvConfigValue(MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
709
- const shouldAutoDismissKeyboard = (null == options ? void 0 : options.autoDismissKeyboard) ?? (null == (_this_options1 = this.options) ? void 0 : _this_options1.autoDismissKeyboard) ?? true;
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 = 1000) {
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
- var _this_options;
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
- var _this_options;
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 (null == displayMatch ? void 0 : displayMatch[1]) {
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 = (null == options ? void 0 : options.keyboardDismissStrategy) ?? (null == (_this_options = this.options) ? void 0 : _this_options.keyboardDismissStrategy) ?? 'esc-first';
858
+ const keyboardDismissStrategy = options?.keyboardDismissStrategy ?? this.options?.keyboardDismissStrategy ?? 'esc-first';
895
859
  const keyboardStatus = await adb.isSoftKeyboardPresent();
896
- const isKeyboardShown = 'boolean' == typeof keyboardStatus ? keyboardStatus : null == keyboardStatus ? void 0 : keyboardStatus.isKeyboardShown;
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 : null == currentStatus ? void 0 : currentStatus.isKeyboardShown;
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 = null == options ? void 0 : options.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
- async launch(uri) {
967
- const device = this.page;
968
- await device.launch(uri);
969
- }
970
- async runAdbShell(command) {
971
- const adb = await this.page.getAdb();
972
- return await adb.shell(command);
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 ?? (null == (_this_options = this.options) ? void 0 : _this_options.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 = null == param ? void 0 : param.scrollType;
124
- if ('untilTop' === scrollToEventName) await this.scrollUntilTop(startingPoint);
125
- else if ('untilBottom' === scrollToEventName) await this.scrollUntilBottom(startingPoint);
126
- else if ('untilRight' === scrollToEventName) await this.scrollUntilRight(startingPoint);
127
- else if ('untilLeft' === scrollToEventName) await this.scrollUntilLeft(startingPoint);
128
- else if ('once' !== scrollToEventName && scrollToEventName) throw new Error(`Unknown scroll event type: ${scrollToEventName}, param: ${JSON.stringify(param)}`);
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 ((null == param ? void 0 : param.direction) !== 'down' && param && param.direction) if ('up' === param.direction) await this.scrollUp(param.distance || void 0, startingPoint);
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((null == param ? void 0 : param.distance) || void 0, startingPoint);
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, null == param ? void 0 : param.duration);
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
- var _this_options, _this_options1, _this_options2;
243
- const androidAdbPath = (null == (_this_options = this.options) ? void 0 : _this_options.androidAdbPath) || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ADB_PATH);
244
- const remoteAdbHost = (null == (_this_options1 = this.options) ? void 0 : _this_options1.remoteAdbHost) || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ADB_REMOTE_HOST);
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] ? " \u2705" : ''}`).join('\n')}
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
- var _this_options, _this_options1;
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 (null == (_this_options1 = this.options) ? void 0 : _this_options1.displayId)) try {
340
- var _this_options2;
314
+ if ('number' == typeof this.options?.displayId) try {
341
315
  const stdout = await adb.shell('dumpsys display');
342
- if (null == (_this_options2 = this.options) ? void 0 : _this_options2.usePhysicalDisplayIdForDisplayLookup) {
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 (null == (_this_options = this.options) ? void 0 : _this_options.displayId)) try {
435
- var _this_options1;
410
+ if ('number' == typeof this.options?.displayId) try {
436
411
  const stdout = await adb.shell('dumpsys display');
437
- if (null == (_this_options1 = this.options) ? void 0 : _this_options1.usePhysicalDisplayIdForDisplayLookup) {
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
- var _this_options;
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 width = Number.parseInt(match[isLandscape ? 2 : 1], 10);
504
- const height = Number.parseInt(match[isLandscape ? 1 : 2], 10);
505
- const scale = (null == (_this_options = this.options) ? void 0 : _this_options.screenshotResizeScale) ?? 1 / this.devicePixelRatio;
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 (null == (_this_options = this.options) ? void 0 : _this_options.displayId);
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 = (null == (_this_options2 = this.options) ? void 0 : _this_options2.usePhysicalDisplayIdForScreenshot) ? await this.getPhysicalDisplayId() : null == (_this_options3 = this.options) ? void 0 : _this_options3.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 = ((null == (_this_options = this.options) ? void 0 : _this_options.imeStrategy) || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
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 = ((null == (_this_options = this.options) ? void 0 : _this_options.imeStrategy) || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
755
- const shouldAutoDismissKeyboard = (null == options ? void 0 : options.autoDismissKeyboard) ?? (null == (_this_options1 = this.options) ? void 0 : _this_options1.autoDismissKeyboard) ?? true;
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 = 1000) {
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
- var _this_options;
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
- var _this_options;
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 (null == displayMatch ? void 0 : displayMatch[1]) {
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 = (null == options ? void 0 : options.keyboardDismissStrategy) ?? (null == (_this_options = this.options) ? void 0 : _this_options.keyboardDismissStrategy) ?? 'esc-first';
904
+ const keyboardDismissStrategy = options?.keyboardDismissStrategy ?? this.options?.keyboardDismissStrategy ?? 'esc-first';
941
905
  const keyboardStatus = await adb.isSoftKeyboardPresent();
942
- const isKeyboardShown = 'boolean' == typeof keyboardStatus ? keyboardStatus : null == keyboardStatus ? void 0 : keyboardStatus.isKeyboardShown;
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 : null == currentStatus ? void 0 : currentStatus.isKeyboardShown;
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 = null == options ? void 0 : options.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
- async launch(uri) {
1014
- const device = this.page;
1015
- await device.launch(uri);
1031
+ createActionWrapper(name) {
1032
+ const action = this.wrapActionInActionSpace(name);
1033
+ return (...args)=>action(args[0]);
1016
1034
  }
1017
- async runAdbShell(command) {
1018
- const adb = await this.page.getAdb();
1019
- return await adb.shell(command);
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 __webpack_i__ in __webpack_exports__)if (-1 === [
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(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
1066
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
1055
1067
  Object.defineProperty(exports, '__esModule', {
1056
1068
  value: true
1057
1069
  });
@@ -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
- launch(uri: string): Promise<void>;
17
- runAdbShell(command: string): Promise<string>;
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 AndroidDeviceInputOpt = {
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
- export declare function getConnectedDevices(): Promise<Device[]>;
147
+ declare type DeviceActionAndroidHomeButton = DeviceAction<undefined, void>;
148
+
149
+ declare type DeviceActionAndroidRecentAppsButton = DeviceAction<undefined, void>;
136
150
 
137
- declare const IME_STRATEGY_ALWAYS_YADB: "always-yadb";
151
+ declare type DeviceActionLaunch = DeviceAction<LaunchParam, void>;
138
152
 
139
- declare const IME_STRATEGY_YADB_FOR_NON_ASCII: "yadb-for-non-ascii";
153
+ declare type DeviceActionRunAdbShell = DeviceAction<RunAdbShellParam, string>;
154
+
155
+ export declare function getConnectedDevices(): Promise<Device[]>;
140
156
 
141
- declare type ImeStrategy = typeof IME_STRATEGY_ALWAYS_YADB | typeof IME_STRATEGY_YADB_FOR_NON_ASCII;
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.10",
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.10",
31
- "@midscene/shared": "0.30.10"
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.11.2",
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
- "@midscene/playground": "0.30.10"
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",