@midscene/android 1.5.2 → 1.5.3-beta-20260305031559.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/cli.mjs CHANGED
@@ -8,6 +8,7 @@ import { BaseMidsceneTools } from "@midscene/shared/mcp";
8
8
  import { Agent } from "@midscene/core/agent";
9
9
  import { mergeAndNormalizeAppNameMapping, normalizeForComparison, repeat } from "@midscene/shared/utils";
10
10
  import node_assert from "node:assert";
11
+ import { execFile } from "node:child_process";
11
12
  import { defineAction, defineActionClearInput, defineActionCursorMove, defineActionDoubleClick, defineActionDragAndDrop, defineActionKeyboardPress, defineActionScroll, defineActionSwipe, defineActionTap, normalizeMobileSwipeParam } from "@midscene/core/device";
12
13
  import { getTmpFile, sleep } from "@midscene/core/utils";
13
14
  import { MIDSCENE_ADB_PATH, MIDSCENE_ADB_REMOTE_HOST, MIDSCENE_ADB_REMOTE_PORT, MIDSCENE_ANDROID_IME_STRATEGY, globalConfigManager } from "@midscene/shared/env";
@@ -1171,14 +1172,23 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1171
1172
  const androidScreenshotPath = `/data/local/tmp/ms_${screenshotId}.png`;
1172
1173
  const useShellScreencap = 'number' == typeof this.options?.displayId;
1173
1174
  try {
1174
- if (useShellScreencap) throw new Error('Using shell screencap for displayId');
1175
- debugDevice('Taking screenshot via adb.takeScreenshot');
1176
- screenshotBuffer = await adb.takeScreenshot(null);
1177
- debugDevice('adb.takeScreenshot completed');
1178
- if (!screenshotBuffer) throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1179
- if (!isValidImageBuffer(screenshotBuffer)) {
1180
- debugDevice('Invalid image buffer detected: not a valid image format');
1181
- throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
1175
+ if (!useShellScreencap && this.takeScreenshotFailCount < AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) {
1176
+ debugDevice('Taking screenshot via adb.takeScreenshot');
1177
+ screenshotBuffer = await adb.takeScreenshot(null);
1178
+ debugDevice('adb.takeScreenshot completed');
1179
+ if (!screenshotBuffer) {
1180
+ this.takeScreenshotFailCount++;
1181
+ throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1182
+ }
1183
+ if (!isValidImageBuffer(screenshotBuffer)) {
1184
+ debugDevice('Invalid image buffer detected: not a valid image format');
1185
+ this.takeScreenshotFailCount++;
1186
+ throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
1187
+ }
1188
+ this.takeScreenshotFailCount = 0;
1189
+ } else {
1190
+ if (this.takeScreenshotFailCount >= AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) debugDevice('Skipping takeScreenshot (failed %d consecutive times), using shell screencap directly', this.takeScreenshotFailCount);
1191
+ throw new Error('Using shell screencap directly');
1182
1192
  }
1183
1193
  const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
1184
1194
  if (validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) {
@@ -1210,9 +1220,16 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1210
1220
  if (!isValidImageBuffer(screenshotBuffer)) throw new Error('Fallback screenshot buffer has invalid PNG format');
1211
1221
  debugDevice(`Fallback screenshot validated successfully: ${screenshotBuffer.length} bytes`);
1212
1222
  } finally{
1213
- Promise.resolve().then(()=>adb.shell(`rm ${androidScreenshotPath}`)).catch((error)=>{
1214
- debugDevice(`Failed to delete remote screenshot: ${error}`);
1215
- });
1223
+ const adbPath = adb.executable?.path || 'adb';
1224
+ const child = execFile(adbPath, [
1225
+ '-s',
1226
+ this.deviceId,
1227
+ 'shell',
1228
+ `rm ${androidScreenshotPath}`
1229
+ ], {
1230
+ timeout: 3000
1231
+ }, ()=>{});
1232
+ child.unref();
1216
1233
  }
1217
1234
  }
1218
1235
  if (!screenshotBuffer) throw new Error('Failed to capture screenshot: all methods failed');
@@ -1656,6 +1673,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1656
1673
  device_define_property(this, "scrcpyAdapter", null);
1657
1674
  device_define_property(this, "appNameMapping", {});
1658
1675
  device_define_property(this, "cachedAdjustScale", null);
1676
+ device_define_property(this, "takeScreenshotFailCount", 0);
1659
1677
  device_define_property(this, "interfaceType", 'android');
1660
1678
  device_define_property(this, "uri", void 0);
1661
1679
  device_define_property(this, "options", void 0);
@@ -1665,6 +1683,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1665
1683
  this.customActions = options?.customActions;
1666
1684
  }
1667
1685
  }
1686
+ device_define_property(AndroidDevice, "TAKE_SCREENSHOT_FAIL_THRESHOLD", 3);
1668
1687
  const runAdbShellParamSchema = z.object({
1669
1688
  command: z.string().describe('ADB shell command to execute')
1670
1689
  });
package/dist/es/index.mjs CHANGED
@@ -3,6 +3,7 @@ import * as __rspack_external_node_fs_5ea92f0c from "node:fs";
3
3
  import * as __rspack_external_node_module_ab9f2194 from "node:module";
4
4
  import * as __rspack_external_node_path_c5b9b54f from "node:path";
5
5
  import node_assert from "node:assert";
6
+ import { execFile } from "node:child_process";
6
7
  import { getMidsceneLocationSchema, z } from "@midscene/core";
7
8
  import { defineAction, defineActionClearInput, defineActionCursorMove, defineActionDoubleClick, defineActionDragAndDrop, defineActionKeyboardPress, defineActionScroll, defineActionSwipe, defineActionTap, normalizeMobileSwipeParam } from "@midscene/core/device";
8
9
  import { getTmpFile, sleep } from "@midscene/core/utils";
@@ -1073,14 +1074,23 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1073
1074
  const androidScreenshotPath = `/data/local/tmp/ms_${screenshotId}.png`;
1074
1075
  const useShellScreencap = 'number' == typeof this.options?.displayId;
1075
1076
  try {
1076
- if (useShellScreencap) throw new Error('Using shell screencap for displayId');
1077
- debugDevice('Taking screenshot via adb.takeScreenshot');
1078
- screenshotBuffer = await adb.takeScreenshot(null);
1079
- debugDevice('adb.takeScreenshot completed');
1080
- if (!screenshotBuffer) throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1081
- if (!isValidImageBuffer(screenshotBuffer)) {
1082
- debugDevice('Invalid image buffer detected: not a valid image format');
1083
- throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
1077
+ if (!useShellScreencap && this.takeScreenshotFailCount < AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) {
1078
+ debugDevice('Taking screenshot via adb.takeScreenshot');
1079
+ screenshotBuffer = await adb.takeScreenshot(null);
1080
+ debugDevice('adb.takeScreenshot completed');
1081
+ if (!screenshotBuffer) {
1082
+ this.takeScreenshotFailCount++;
1083
+ throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1084
+ }
1085
+ if (!isValidImageBuffer(screenshotBuffer)) {
1086
+ debugDevice('Invalid image buffer detected: not a valid image format');
1087
+ this.takeScreenshotFailCount++;
1088
+ throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
1089
+ }
1090
+ this.takeScreenshotFailCount = 0;
1091
+ } else {
1092
+ if (this.takeScreenshotFailCount >= AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) debugDevice('Skipping takeScreenshot (failed %d consecutive times), using shell screencap directly', this.takeScreenshotFailCount);
1093
+ throw new Error('Using shell screencap directly');
1084
1094
  }
1085
1095
  const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
1086
1096
  if (validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) {
@@ -1112,9 +1122,16 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1112
1122
  if (!isValidImageBuffer(screenshotBuffer)) throw new Error('Fallback screenshot buffer has invalid PNG format');
1113
1123
  debugDevice(`Fallback screenshot validated successfully: ${screenshotBuffer.length} bytes`);
1114
1124
  } finally{
1115
- Promise.resolve().then(()=>adb.shell(`rm ${androidScreenshotPath}`)).catch((error)=>{
1116
- debugDevice(`Failed to delete remote screenshot: ${error}`);
1117
- });
1125
+ const adbPath = adb.executable?.path || 'adb';
1126
+ const child = execFile(adbPath, [
1127
+ '-s',
1128
+ this.deviceId,
1129
+ 'shell',
1130
+ `rm ${androidScreenshotPath}`
1131
+ ], {
1132
+ timeout: 3000
1133
+ }, ()=>{});
1134
+ child.unref();
1118
1135
  }
1119
1136
  }
1120
1137
  if (!screenshotBuffer) throw new Error('Failed to capture screenshot: all methods failed');
@@ -1558,6 +1575,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1558
1575
  device_define_property(this, "scrcpyAdapter", null);
1559
1576
  device_define_property(this, "appNameMapping", {});
1560
1577
  device_define_property(this, "cachedAdjustScale", null);
1578
+ device_define_property(this, "takeScreenshotFailCount", 0);
1561
1579
  device_define_property(this, "interfaceType", 'android');
1562
1580
  device_define_property(this, "uri", void 0);
1563
1581
  device_define_property(this, "options", void 0);
@@ -1567,6 +1585,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1567
1585
  this.customActions = options?.customActions;
1568
1586
  }
1569
1587
  }
1588
+ device_define_property(AndroidDevice, "TAKE_SCREENSHOT_FAIL_THRESHOLD", 3);
1570
1589
  const runAdbShellParamSchema = z.object({
1571
1590
  command: z.string().describe('ADB shell command to execute')
1572
1591
  });
@@ -6,6 +6,7 @@ import { BaseMCPServer, BaseMidsceneTools, createMCPServerLauncher } from "@mids
6
6
  import { Agent } from "@midscene/core/agent";
7
7
  import { mergeAndNormalizeAppNameMapping, normalizeForComparison, repeat } from "@midscene/shared/utils";
8
8
  import node_assert from "node:assert";
9
+ import { execFile } from "node:child_process";
9
10
  import { getMidsceneLocationSchema, z } from "@midscene/core";
10
11
  import { defineAction, defineActionClearInput, defineActionCursorMove, defineActionDoubleClick, defineActionDragAndDrop, defineActionKeyboardPress, defineActionScroll, defineActionSwipe, defineActionTap, normalizeMobileSwipeParam } from "@midscene/core/device";
11
12
  import { getTmpFile, sleep } from "@midscene/core/utils";
@@ -1170,14 +1171,23 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1170
1171
  const androidScreenshotPath = `/data/local/tmp/ms_${screenshotId}.png`;
1171
1172
  const useShellScreencap = 'number' == typeof this.options?.displayId;
1172
1173
  try {
1173
- if (useShellScreencap) throw new Error('Using shell screencap for displayId');
1174
- debugDevice('Taking screenshot via adb.takeScreenshot');
1175
- screenshotBuffer = await adb.takeScreenshot(null);
1176
- debugDevice('adb.takeScreenshot completed');
1177
- if (!screenshotBuffer) throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1178
- if (!isValidImageBuffer(screenshotBuffer)) {
1179
- debugDevice('Invalid image buffer detected: not a valid image format');
1180
- throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
1174
+ if (!useShellScreencap && this.takeScreenshotFailCount < AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) {
1175
+ debugDevice('Taking screenshot via adb.takeScreenshot');
1176
+ screenshotBuffer = await adb.takeScreenshot(null);
1177
+ debugDevice('adb.takeScreenshot completed');
1178
+ if (!screenshotBuffer) {
1179
+ this.takeScreenshotFailCount++;
1180
+ throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1181
+ }
1182
+ if (!isValidImageBuffer(screenshotBuffer)) {
1183
+ debugDevice('Invalid image buffer detected: not a valid image format');
1184
+ this.takeScreenshotFailCount++;
1185
+ throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
1186
+ }
1187
+ this.takeScreenshotFailCount = 0;
1188
+ } else {
1189
+ if (this.takeScreenshotFailCount >= AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) debugDevice('Skipping takeScreenshot (failed %d consecutive times), using shell screencap directly', this.takeScreenshotFailCount);
1190
+ throw new Error('Using shell screencap directly');
1181
1191
  }
1182
1192
  const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
1183
1193
  if (validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) {
@@ -1209,9 +1219,16 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1209
1219
  if (!isValidImageBuffer(screenshotBuffer)) throw new Error('Fallback screenshot buffer has invalid PNG format');
1210
1220
  debugDevice(`Fallback screenshot validated successfully: ${screenshotBuffer.length} bytes`);
1211
1221
  } finally{
1212
- Promise.resolve().then(()=>adb.shell(`rm ${androidScreenshotPath}`)).catch((error)=>{
1213
- debugDevice(`Failed to delete remote screenshot: ${error}`);
1214
- });
1222
+ const adbPath = adb.executable?.path || 'adb';
1223
+ const child = execFile(adbPath, [
1224
+ '-s',
1225
+ this.deviceId,
1226
+ 'shell',
1227
+ `rm ${androidScreenshotPath}`
1228
+ ], {
1229
+ timeout: 3000
1230
+ }, ()=>{});
1231
+ child.unref();
1215
1232
  }
1216
1233
  }
1217
1234
  if (!screenshotBuffer) throw new Error('Failed to capture screenshot: all methods failed');
@@ -1655,6 +1672,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1655
1672
  device_define_property(this, "scrcpyAdapter", null);
1656
1673
  device_define_property(this, "appNameMapping", {});
1657
1674
  device_define_property(this, "cachedAdjustScale", null);
1675
+ device_define_property(this, "takeScreenshotFailCount", 0);
1658
1676
  device_define_property(this, "interfaceType", 'android');
1659
1677
  device_define_property(this, "uri", void 0);
1660
1678
  device_define_property(this, "options", void 0);
@@ -1664,6 +1682,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1664
1682
  this.customActions = options?.customActions;
1665
1683
  }
1666
1684
  }
1685
+ device_define_property(AndroidDevice, "TAKE_SCREENSHOT_FAIL_THRESHOLD", 3);
1667
1686
  const runAdbShellParamSchema = z.object({
1668
1687
  command: z.string().describe('ADB shell command to execute')
1669
1688
  });
package/dist/lib/cli.js CHANGED
@@ -525,6 +525,7 @@ var __webpack_exports__ = {};
525
525
  };
526
526
  const external_node_assert_namespaceObject = require("node:assert");
527
527
  var external_node_assert_default = /*#__PURE__*/ __webpack_require__.n(external_node_assert_namespaceObject);
528
+ const external_node_child_process_namespaceObject = require("node:child_process");
528
529
  var external_node_fs_ = __webpack_require__("node:fs");
529
530
  var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_);
530
531
  var external_node_module_ = __webpack_require__("node:module");
@@ -1186,14 +1187,23 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1186
1187
  const androidScreenshotPath = `/data/local/tmp/ms_${screenshotId}.png`;
1187
1188
  const useShellScreencap = 'number' == typeof this.options?.displayId;
1188
1189
  try {
1189
- if (useShellScreencap) throw new Error('Using shell screencap for displayId');
1190
- debugDevice('Taking screenshot via adb.takeScreenshot');
1191
- screenshotBuffer = await adb.takeScreenshot(null);
1192
- debugDevice('adb.takeScreenshot completed');
1193
- if (!screenshotBuffer) throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1194
- if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) {
1195
- debugDevice('Invalid image buffer detected: not a valid image format');
1196
- throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
1190
+ if (!useShellScreencap && this.takeScreenshotFailCount < AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) {
1191
+ debugDevice('Taking screenshot via adb.takeScreenshot');
1192
+ screenshotBuffer = await adb.takeScreenshot(null);
1193
+ debugDevice('adb.takeScreenshot completed');
1194
+ if (!screenshotBuffer) {
1195
+ this.takeScreenshotFailCount++;
1196
+ throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1197
+ }
1198
+ if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) {
1199
+ debugDevice('Invalid image buffer detected: not a valid image format');
1200
+ this.takeScreenshotFailCount++;
1201
+ throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
1202
+ }
1203
+ this.takeScreenshotFailCount = 0;
1204
+ } else {
1205
+ if (this.takeScreenshotFailCount >= AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) debugDevice('Skipping takeScreenshot (failed %d consecutive times), using shell screencap directly', this.takeScreenshotFailCount);
1206
+ throw new Error('Using shell screencap directly');
1197
1207
  }
1198
1208
  const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
1199
1209
  if (validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) {
@@ -1225,9 +1235,16 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1225
1235
  if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) throw new Error('Fallback screenshot buffer has invalid PNG format');
1226
1236
  debugDevice(`Fallback screenshot validated successfully: ${screenshotBuffer.length} bytes`);
1227
1237
  } finally{
1228
- Promise.resolve().then(()=>adb.shell(`rm ${androidScreenshotPath}`)).catch((error)=>{
1229
- debugDevice(`Failed to delete remote screenshot: ${error}`);
1230
- });
1238
+ const adbPath = adb.executable?.path || 'adb';
1239
+ const child = (0, external_node_child_process_namespaceObject.execFile)(adbPath, [
1240
+ '-s',
1241
+ this.deviceId,
1242
+ 'shell',
1243
+ `rm ${androidScreenshotPath}`
1244
+ ], {
1245
+ timeout: 3000
1246
+ }, ()=>{});
1247
+ child.unref();
1231
1248
  }
1232
1249
  }
1233
1250
  if (!screenshotBuffer) throw new Error('Failed to capture screenshot: all methods failed');
@@ -1671,6 +1688,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1671
1688
  device_define_property(this, "scrcpyAdapter", null);
1672
1689
  device_define_property(this, "appNameMapping", {});
1673
1690
  device_define_property(this, "cachedAdjustScale", null);
1691
+ device_define_property(this, "takeScreenshotFailCount", 0);
1674
1692
  device_define_property(this, "interfaceType", 'android');
1675
1693
  device_define_property(this, "uri", void 0);
1676
1694
  device_define_property(this, "options", void 0);
@@ -1680,6 +1698,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1680
1698
  this.customActions = options?.customActions;
1681
1699
  }
1682
1700
  }
1701
+ device_define_property(AndroidDevice, "TAKE_SCREENSHOT_FAIL_THRESHOLD", 3);
1683
1702
  const runAdbShellParamSchema = core_namespaceObject.z.object({
1684
1703
  command: core_namespaceObject.z.string().describe('ADB shell command to execute')
1685
1704
  });
package/dist/lib/index.js CHANGED
@@ -442,6 +442,7 @@ var __webpack_exports__ = {};
442
442
  });
443
443
  const external_node_assert_namespaceObject = require("node:assert");
444
444
  var external_node_assert_default = /*#__PURE__*/ __webpack_require__.n(external_node_assert_namespaceObject);
445
+ const external_node_child_process_namespaceObject = require("node:child_process");
445
446
  var external_node_fs_ = __webpack_require__("node:fs");
446
447
  var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_);
447
448
  var external_node_module_ = __webpack_require__("node:module");
@@ -1106,14 +1107,23 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1106
1107
  const androidScreenshotPath = `/data/local/tmp/ms_${screenshotId}.png`;
1107
1108
  const useShellScreencap = 'number' == typeof this.options?.displayId;
1108
1109
  try {
1109
- if (useShellScreencap) throw new Error('Using shell screencap for displayId');
1110
- debugDevice('Taking screenshot via adb.takeScreenshot');
1111
- screenshotBuffer = await adb.takeScreenshot(null);
1112
- debugDevice('adb.takeScreenshot completed');
1113
- if (!screenshotBuffer) throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1114
- if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) {
1115
- debugDevice('Invalid image buffer detected: not a valid image format');
1116
- throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
1110
+ if (!useShellScreencap && this.takeScreenshotFailCount < AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) {
1111
+ debugDevice('Taking screenshot via adb.takeScreenshot');
1112
+ screenshotBuffer = await adb.takeScreenshot(null);
1113
+ debugDevice('adb.takeScreenshot completed');
1114
+ if (!screenshotBuffer) {
1115
+ this.takeScreenshotFailCount++;
1116
+ throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1117
+ }
1118
+ if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) {
1119
+ debugDevice('Invalid image buffer detected: not a valid image format');
1120
+ this.takeScreenshotFailCount++;
1121
+ throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
1122
+ }
1123
+ this.takeScreenshotFailCount = 0;
1124
+ } else {
1125
+ if (this.takeScreenshotFailCount >= AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) debugDevice('Skipping takeScreenshot (failed %d consecutive times), using shell screencap directly', this.takeScreenshotFailCount);
1126
+ throw new Error('Using shell screencap directly');
1117
1127
  }
1118
1128
  const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
1119
1129
  if (validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) {
@@ -1145,9 +1155,16 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1145
1155
  if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) throw new Error('Fallback screenshot buffer has invalid PNG format');
1146
1156
  debugDevice(`Fallback screenshot validated successfully: ${screenshotBuffer.length} bytes`);
1147
1157
  } finally{
1148
- Promise.resolve().then(()=>adb.shell(`rm ${androidScreenshotPath}`)).catch((error)=>{
1149
- debugDevice(`Failed to delete remote screenshot: ${error}`);
1150
- });
1158
+ const adbPath = adb.executable?.path || 'adb';
1159
+ const child = (0, external_node_child_process_namespaceObject.execFile)(adbPath, [
1160
+ '-s',
1161
+ this.deviceId,
1162
+ 'shell',
1163
+ `rm ${androidScreenshotPath}`
1164
+ ], {
1165
+ timeout: 3000
1166
+ }, ()=>{});
1167
+ child.unref();
1151
1168
  }
1152
1169
  }
1153
1170
  if (!screenshotBuffer) throw new Error('Failed to capture screenshot: all methods failed');
@@ -1591,6 +1608,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1591
1608
  device_define_property(this, "scrcpyAdapter", null);
1592
1609
  device_define_property(this, "appNameMapping", {});
1593
1610
  device_define_property(this, "cachedAdjustScale", null);
1611
+ device_define_property(this, "takeScreenshotFailCount", 0);
1594
1612
  device_define_property(this, "interfaceType", 'android');
1595
1613
  device_define_property(this, "uri", void 0);
1596
1614
  device_define_property(this, "options", void 0);
@@ -1600,6 +1618,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1600
1618
  this.customActions = options?.customActions;
1601
1619
  }
1602
1620
  }
1621
+ device_define_property(AndroidDevice, "TAKE_SCREENSHOT_FAIL_THRESHOLD", 3);
1603
1622
  const runAdbShellParamSchema = core_namespaceObject.z.object({
1604
1623
  command: core_namespaceObject.z.string().describe('ADB shell command to execute')
1605
1624
  });
@@ -539,6 +539,7 @@ var __webpack_exports__ = {};
539
539
  };
540
540
  const external_node_assert_namespaceObject = require("node:assert");
541
541
  var external_node_assert_default = /*#__PURE__*/ __webpack_require__.n(external_node_assert_namespaceObject);
542
+ const external_node_child_process_namespaceObject = require("node:child_process");
542
543
  var external_node_fs_ = __webpack_require__("node:fs");
543
544
  var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_);
544
545
  var external_node_module_ = __webpack_require__("node:module");
@@ -1201,14 +1202,23 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1201
1202
  const androidScreenshotPath = `/data/local/tmp/ms_${screenshotId}.png`;
1202
1203
  const useShellScreencap = 'number' == typeof this.options?.displayId;
1203
1204
  try {
1204
- if (useShellScreencap) throw new Error('Using shell screencap for displayId');
1205
- debugDevice('Taking screenshot via adb.takeScreenshot');
1206
- screenshotBuffer = await adb.takeScreenshot(null);
1207
- debugDevice('adb.takeScreenshot completed');
1208
- if (!screenshotBuffer) throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1209
- if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) {
1210
- debugDevice('Invalid image buffer detected: not a valid image format');
1211
- throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
1205
+ if (!useShellScreencap && this.takeScreenshotFailCount < AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) {
1206
+ debugDevice('Taking screenshot via adb.takeScreenshot');
1207
+ screenshotBuffer = await adb.takeScreenshot(null);
1208
+ debugDevice('adb.takeScreenshot completed');
1209
+ if (!screenshotBuffer) {
1210
+ this.takeScreenshotFailCount++;
1211
+ throw new Error('Failed to capture screenshot: screenshotBuffer is null');
1212
+ }
1213
+ if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) {
1214
+ debugDevice('Invalid image buffer detected: not a valid image format');
1215
+ this.takeScreenshotFailCount++;
1216
+ throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
1217
+ }
1218
+ this.takeScreenshotFailCount = 0;
1219
+ } else {
1220
+ if (this.takeScreenshotFailCount >= AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) debugDevice('Skipping takeScreenshot (failed %d consecutive times), using shell screencap directly', this.takeScreenshotFailCount);
1221
+ throw new Error('Using shell screencap directly');
1212
1222
  }
1213
1223
  const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
1214
1224
  if (validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) {
@@ -1240,9 +1250,16 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1240
1250
  if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) throw new Error('Fallback screenshot buffer has invalid PNG format');
1241
1251
  debugDevice(`Fallback screenshot validated successfully: ${screenshotBuffer.length} bytes`);
1242
1252
  } finally{
1243
- Promise.resolve().then(()=>adb.shell(`rm ${androidScreenshotPath}`)).catch((error)=>{
1244
- debugDevice(`Failed to delete remote screenshot: ${error}`);
1245
- });
1253
+ const adbPath = adb.executable?.path || 'adb';
1254
+ const child = (0, external_node_child_process_namespaceObject.execFile)(adbPath, [
1255
+ '-s',
1256
+ this.deviceId,
1257
+ 'shell',
1258
+ `rm ${androidScreenshotPath}`
1259
+ ], {
1260
+ timeout: 3000
1261
+ }, ()=>{});
1262
+ child.unref();
1246
1263
  }
1247
1264
  }
1248
1265
  if (!screenshotBuffer) throw new Error('Failed to capture screenshot: all methods failed');
@@ -1686,6 +1703,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1686
1703
  device_define_property(this, "scrcpyAdapter", null);
1687
1704
  device_define_property(this, "appNameMapping", {});
1688
1705
  device_define_property(this, "cachedAdjustScale", null);
1706
+ device_define_property(this, "takeScreenshotFailCount", 0);
1689
1707
  device_define_property(this, "interfaceType", 'android');
1690
1708
  device_define_property(this, "uri", void 0);
1691
1709
  device_define_property(this, "options", void 0);
@@ -1695,6 +1713,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
1695
1713
  this.customActions = options?.customActions;
1696
1714
  }
1697
1715
  }
1716
+ device_define_property(AndroidDevice, "TAKE_SCREENSHOT_FAIL_THRESHOLD", 3);
1698
1717
  const runAdbShellParamSchema = core_namespaceObject.z.object({
1699
1718
  command: core_namespaceObject.z.string().describe('ADB shell command to execute')
1700
1719
  });
@@ -74,6 +74,8 @@ export declare class AndroidDevice implements AbstractInterface {
74
74
  private scrcpyAdapter;
75
75
  private appNameMapping;
76
76
  private cachedAdjustScale;
77
+ private takeScreenshotFailCount;
78
+ private static readonly TAKE_SCREENSHOT_FAIL_THRESHOLD;
77
79
  interfaceType: InterfaceType;
78
80
  uri: string | undefined;
79
81
  options?: AndroidDeviceOpt;
@@ -75,6 +75,8 @@ declare class AndroidDevice implements AbstractInterface {
75
75
  private scrcpyAdapter;
76
76
  private appNameMapping;
77
77
  private cachedAdjustScale;
78
+ private takeScreenshotFailCount;
79
+ private static readonly TAKE_SCREENSHOT_FAIL_THRESHOLD;
78
80
  interfaceType: InterfaceType;
79
81
  uri: string | undefined;
80
82
  options?: AndroidDeviceOpt;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@midscene/android",
3
- "version": "1.5.2",
3
+ "version": "1.5.3-beta-20260305031559.0",
4
4
  "description": "Android automation library for Midscene",
5
5
  "keywords": [
6
6
  "Android UI automation",
@@ -41,8 +41,8 @@
41
41
  "@yume-chan/stream-extra": "^1.0.0",
42
42
  "appium-adb": "12.12.1",
43
43
  "sharp": "^0.34.3",
44
- "@midscene/core": "1.5.2",
45
- "@midscene/shared": "1.5.2"
44
+ "@midscene/core": "1.5.3-beta-20260305031559.0",
45
+ "@midscene/shared": "1.5.3-beta-20260305031559.0"
46
46
  },
47
47
  "optionalDependencies": {
48
48
  "@ffmpeg-installer/ffmpeg": "^1.1.0"
@@ -56,7 +56,7 @@
56
56
  "tsx": "^4.19.2",
57
57
  "vitest": "3.0.5",
58
58
  "zod": "3.24.3",
59
- "@midscene/playground": "1.5.2"
59
+ "@midscene/playground": "1.5.3-beta-20260305031559.0"
60
60
  },
61
61
  "license": "MIT",
62
62
  "scripts": {