@aiscene/android 1.6.6 → 1.6.8
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/LICENSE +21 -0
- package/dist/es/cli.mjs +125 -90
- package/dist/es/index.mjs +124 -89
- package/dist/es/mcp-server.mjs +125 -90
- package/dist/lib/cli.js +123 -88
- package/dist/lib/index.js +122 -87
- package/dist/lib/mcp-server.js +123 -88
- package/dist/types/index.d.ts +40 -31
- package/dist/types/mcp-server.d.ts +40 -31
- package/package.json +24 -21
package/dist/lib/cli.js
CHANGED
|
@@ -550,6 +550,7 @@ var __webpack_exports__ = {};
|
|
|
550
550
|
};
|
|
551
551
|
const external_node_assert_namespaceObject = require("node:assert");
|
|
552
552
|
var external_node_assert_default = /*#__PURE__*/ __webpack_require__.n(external_node_assert_namespaceObject);
|
|
553
|
+
const external_node_child_process_namespaceObject = require("node:child_process");
|
|
553
554
|
var external_node_fs_ = __webpack_require__("node:fs");
|
|
554
555
|
var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_);
|
|
555
556
|
var external_node_module_ = __webpack_require__("node:module");
|
|
@@ -690,6 +691,9 @@ var __webpack_exports__ = {};
|
|
|
690
691
|
const IME_STRATEGY_ALWAYS_YADB = 'always-yadb';
|
|
691
692
|
const IME_STRATEGY_YADB_FOR_NON_ASCII = 'yadb-for-non-ascii';
|
|
692
693
|
const debugDevice = (0, logger_.getDebug)('android:device');
|
|
694
|
+
function escapeForShell(text) {
|
|
695
|
+
return text.replace(/'/g, "'\\''").replace(/\n/g, '\\n');
|
|
696
|
+
}
|
|
693
697
|
class AndroidDevice {
|
|
694
698
|
actionSpace() {
|
|
695
699
|
const defaultActions = [
|
|
@@ -717,6 +721,12 @@ var __webpack_exports__ = {};
|
|
|
717
721
|
]).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.')),
|
|
718
722
|
locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The input field to be filled').optional()
|
|
719
723
|
}),
|
|
724
|
+
sample: {
|
|
725
|
+
value: 'test@example.com',
|
|
726
|
+
locate: {
|
|
727
|
+
prompt: 'the email input field'
|
|
728
|
+
}
|
|
729
|
+
},
|
|
720
730
|
call: async (param)=>{
|
|
721
731
|
const element = param.locate;
|
|
722
732
|
if ('typeOnly' !== param.mode) await this.clearInput(element);
|
|
@@ -762,9 +772,21 @@ var __webpack_exports__ = {};
|
|
|
762
772
|
y: to.center[1]
|
|
763
773
|
});
|
|
764
774
|
}),
|
|
775
|
+
(0, device_namespaceObject.defineActionSwipe)(async (param)=>{
|
|
776
|
+
const { startPoint, endPoint, duration, repeatCount } = (0, device_namespaceObject.normalizeMobileSwipeParam)(param, await this.size());
|
|
777
|
+
for(let i = 0; i < repeatCount; i++)await this.mouseDrag(startPoint, endPoint, duration);
|
|
778
|
+
}),
|
|
765
779
|
(0, device_namespaceObject.defineActionKeyboardPress)(async (param)=>{
|
|
766
780
|
await this.keyboardPress(param.keyName);
|
|
767
781
|
}),
|
|
782
|
+
(0, device_namespaceObject.defineActionCursorMove)(async (param)=>{
|
|
783
|
+
const arrowKey = 'left' === param.direction ? 'ArrowLeft' : 'ArrowRight';
|
|
784
|
+
const times = param.times ?? 1;
|
|
785
|
+
for(let i = 0; i < times; i++){
|
|
786
|
+
await this.keyboardPress(arrowKey);
|
|
787
|
+
await (0, core_utils_namespaceObject.sleep)(100);
|
|
788
|
+
}
|
|
789
|
+
}),
|
|
768
790
|
(0, device_namespaceObject.defineAction)({
|
|
769
791
|
name: 'LongPress',
|
|
770
792
|
description: 'Trigger a long press on the screen at specified element',
|
|
@@ -772,6 +794,11 @@ var __webpack_exports__ = {};
|
|
|
772
794
|
duration: core_namespaceObject.z.number().optional().describe('The duration of the long press in milliseconds'),
|
|
773
795
|
locate: (0, core_namespaceObject.getMidsceneLocationSchema)().describe('The element to be long pressed')
|
|
774
796
|
}),
|
|
797
|
+
sample: {
|
|
798
|
+
locate: {
|
|
799
|
+
prompt: 'the message bubble'
|
|
800
|
+
}
|
|
801
|
+
},
|
|
775
802
|
call: async (param)=>{
|
|
776
803
|
const element = param.locate;
|
|
777
804
|
if (!element) throw new Error('LongPress requires an element to be located');
|
|
@@ -791,6 +818,12 @@ var __webpack_exports__ = {};
|
|
|
791
818
|
duration: core_namespaceObject.z.number().optional().describe('The duration of the pull (in milliseconds)'),
|
|
792
819
|
locate: (0, core_namespaceObject.getMidsceneLocationSchema)().optional().describe('The element to start the pull from (optional)')
|
|
793
820
|
}),
|
|
821
|
+
sample: {
|
|
822
|
+
direction: 'down',
|
|
823
|
+
locate: {
|
|
824
|
+
prompt: 'the center of the content list area'
|
|
825
|
+
}
|
|
826
|
+
},
|
|
794
827
|
call: async (param)=>{
|
|
795
828
|
const element = param.locate;
|
|
796
829
|
const startPoint = element ? {
|
|
@@ -803,6 +836,16 @@ var __webpack_exports__ = {};
|
|
|
803
836
|
else throw new Error(`Unknown pull direction: ${param.direction}`);
|
|
804
837
|
}
|
|
805
838
|
}),
|
|
839
|
+
(0, device_namespaceObject.defineActionPinch)(async (param)=>{
|
|
840
|
+
const { centerX, centerY, startDistance, endDistance, duration } = (0, device_namespaceObject.normalizePinchParam)(param, await this.size());
|
|
841
|
+
const { x: adjCenterX, y: adjCenterY } = await this.adjustCoordinates(centerX, centerY);
|
|
842
|
+
const ratio = 0 !== adjCenterX && 0 !== centerX ? adjCenterX / centerX : 1;
|
|
843
|
+
const adjStartDist = Math.round(startDistance * ratio);
|
|
844
|
+
const adjEndDist = Math.round(endDistance * ratio);
|
|
845
|
+
await this.ensureYadb();
|
|
846
|
+
const adb = await this.getAdb();
|
|
847
|
+
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}`);
|
|
848
|
+
}),
|
|
806
849
|
(0, device_namespaceObject.defineActionClearInput)(async (param)=>{
|
|
807
850
|
await this.clearInput(param.locate);
|
|
808
851
|
})
|
|
@@ -947,61 +990,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
947
990
|
async execYadb(keyboardContent) {
|
|
948
991
|
await this.ensureYadb();
|
|
949
992
|
const adb = await this.getAdb();
|
|
950
|
-
|
|
951
|
-
const command = `app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboard "${keyboardContent}"`;
|
|
952
|
-
debugDevice(`Executing YADB input: "${keyboardContent}"`);
|
|
953
|
-
await adb.shell(command);
|
|
954
|
-
debugDevice(`YADB input completed: "${keyboardContent}"`);
|
|
955
|
-
} catch (error) {
|
|
956
|
-
const isAccessibilityConflict = error?.cause?.stderr?.includes('UiAutomationService') || error?.cause?.stderr?.includes('already registered') || error?.message?.includes('UiAutomationService');
|
|
957
|
-
if (isAccessibilityConflict) {
|
|
958
|
-
debugDevice("YADB failed due to AccessibilityService conflict (likely Appium running), falling back to clipboard method");
|
|
959
|
-
await this.inputViaClipboard(keyboardContent);
|
|
960
|
-
} else debugDevice(`YADB execution may have completed despite error: ${error}`);
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
async inputViaClipboard(text) {
|
|
964
|
-
const adb = await this.getAdb();
|
|
965
|
-
try {
|
|
966
|
-
debugDevice(`Inputting via clipboard: "${text}"`);
|
|
967
|
-
const escapedText = text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
968
|
-
const setClipboardCmd = `
|
|
969
|
-
content insert --uri content://settings/system --bind name:s:clipboard_text --bind value:s:"${escapedText}"
|
|
970
|
-
`;
|
|
971
|
-
await adb.shell(setClipboardCmd);
|
|
972
|
-
await (0, core_utils_namespaceObject.sleep)(100);
|
|
973
|
-
await adb.shell('input keyevent KEYCODE_PASTE');
|
|
974
|
-
await (0, core_utils_namespaceObject.sleep)(100);
|
|
975
|
-
debugDevice(`Clipboard input completed via content provider: "${text}"`);
|
|
976
|
-
} catch (error1) {
|
|
977
|
-
debugDevice(`Content provider clipboard failed, trying clipper app: ${error1}`);
|
|
978
|
-
try {
|
|
979
|
-
const base64Text = Buffer.from(text, 'utf-8').toString('base64');
|
|
980
|
-
await adb.shell(`am broadcast -a clipper.set -e text "${base64Text}"`);
|
|
981
|
-
await (0, core_utils_namespaceObject.sleep)(100);
|
|
982
|
-
await adb.shell('input keyevent KEYCODE_PASTE');
|
|
983
|
-
await (0, core_utils_namespaceObject.sleep)(100);
|
|
984
|
-
debugDevice(`Clipboard input completed via clipper: "${text}"`);
|
|
985
|
-
} catch (error2) {
|
|
986
|
-
debugDevice(`All clipboard methods failed: ${error2}`);
|
|
987
|
-
const isPureAscii = /^[\x00-\x7F]*$/.test(text);
|
|
988
|
-
if (isPureAscii) {
|
|
989
|
-
debugDevice(`Using ADB inputText for ASCII text: "${text}"`);
|
|
990
|
-
await adb.inputText(text);
|
|
991
|
-
} else await this.inputCharByChar(text);
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
async inputCharByChar(text) {
|
|
996
|
-
const adb = await this.getAdb();
|
|
997
|
-
debugDevice(`Inputting character by character (slow method): "${text}"`);
|
|
998
|
-
const chars = Array.from(text);
|
|
999
|
-
for (const char of chars){
|
|
1000
|
-
if (' ' === char) await adb.shell('input keyevent KEYCODE_SPACE');
|
|
1001
|
-
else await adb.shell(`input text "${char}"`);
|
|
1002
|
-
await (0, core_utils_namespaceObject.sleep)(50);
|
|
1003
|
-
}
|
|
1004
|
-
debugDevice("Character-by-character input completed");
|
|
993
|
+
await adb.shell(`app_process${this.getDisplayArg()} -Djava.class.path=/data/local/tmp/yadb /data/local/tmp com.ysbing.yadb.Main -keyboard '${keyboardContent}'`);
|
|
1005
994
|
}
|
|
1006
995
|
async getElementsInfo() {
|
|
1007
996
|
return [];
|
|
@@ -1014,6 +1003,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1014
1003
|
}
|
|
1015
1004
|
async getScreenSize() {
|
|
1016
1005
|
const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
|
|
1006
|
+
debugDevice(`getScreenSize: alwaysRefreshScreenInfo=${this.options?.alwaysRefreshScreenInfo}, shouldCache=${shouldCache}, hasCachedSize=${!!this.cachedScreenSize}`);
|
|
1017
1007
|
if (shouldCache && this.cachedScreenSize) return this.cachedScreenSize;
|
|
1018
1008
|
const adb = await this.getAdb();
|
|
1019
1009
|
if ('number' == typeof this.options?.displayId) try {
|
|
@@ -1147,6 +1137,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1147
1137
|
}
|
|
1148
1138
|
async getDisplayOrientation() {
|
|
1149
1139
|
const shouldCache = !(this.options?.alwaysRefreshScreenInfo ?? false);
|
|
1140
|
+
debugDevice(`getDisplayOrientation: alwaysRefreshScreenInfo=${this.options?.alwaysRefreshScreenInfo}, shouldCache=${shouldCache}, hasCachedOrientation=${null !== this.cachedOrientation}`);
|
|
1150
1141
|
if (shouldCache && null !== this.cachedOrientation) return this.cachedOrientation;
|
|
1151
1142
|
const adb = await this.getAdb();
|
|
1152
1143
|
let orientation = 0;
|
|
@@ -1172,6 +1163,15 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1172
1163
|
if (shouldCache) this.cachedOrientation = orientation;
|
|
1173
1164
|
return orientation;
|
|
1174
1165
|
}
|
|
1166
|
+
async getOrientedPhysicalSize() {
|
|
1167
|
+
const info = await this.getDevicePhysicalInfo();
|
|
1168
|
+
const isLandscape = 1 === info.orientation || 3 === info.orientation;
|
|
1169
|
+
const shouldSwap = true !== info.isCurrentOrientation && isLandscape;
|
|
1170
|
+
return {
|
|
1171
|
+
width: shouldSwap ? info.physicalHeight : info.physicalWidth,
|
|
1172
|
+
height: shouldSwap ? info.physicalWidth : info.physicalHeight
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
1175
|
async size() {
|
|
1176
1176
|
const deviceInfo = await this.getDevicePhysicalInfo();
|
|
1177
1177
|
const adapter = this.getScrcpyAdapter();
|
|
@@ -1198,7 +1198,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1198
1198
|
height: logicalHeight
|
|
1199
1199
|
};
|
|
1200
1200
|
}
|
|
1201
|
-
async cacheFeatureForPoint(center
|
|
1201
|
+
async cacheFeatureForPoint(center) {
|
|
1202
1202
|
const { width, height } = await this.size();
|
|
1203
1203
|
debugDevice('cacheFeatureForPoint: center=[%s,%s], screen=[%s,%s]', center[0], center[1], width, height);
|
|
1204
1204
|
return {
|
|
@@ -1278,14 +1278,23 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1278
1278
|
const androidScreenshotPath = `/data/local/tmp/ms_${screenshotId}.png`;
|
|
1279
1279
|
const useShellScreencap = 'number' == typeof this.options?.displayId;
|
|
1280
1280
|
try {
|
|
1281
|
-
if (useShellScreencap
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1281
|
+
if (!useShellScreencap && this.takeScreenshotFailCount < AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) {
|
|
1282
|
+
debugDevice('Taking screenshot via adb.takeScreenshot');
|
|
1283
|
+
screenshotBuffer = await adb.takeScreenshot(null);
|
|
1284
|
+
debugDevice('adb.takeScreenshot completed');
|
|
1285
|
+
if (!screenshotBuffer) {
|
|
1286
|
+
this.takeScreenshotFailCount++;
|
|
1287
|
+
throw new Error('Failed to capture screenshot: screenshotBuffer is null');
|
|
1288
|
+
}
|
|
1289
|
+
if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) {
|
|
1290
|
+
debugDevice('Invalid image buffer detected: not a valid image format');
|
|
1291
|
+
this.takeScreenshotFailCount++;
|
|
1292
|
+
throw new Error('Screenshot buffer has invalid format: could not find valid image signature');
|
|
1293
|
+
}
|
|
1294
|
+
this.takeScreenshotFailCount = 0;
|
|
1295
|
+
} else {
|
|
1296
|
+
if (this.takeScreenshotFailCount >= AndroidDevice.TAKE_SCREENSHOT_FAIL_THRESHOLD) debugDevice('Skipping takeScreenshot (failed %d consecutive times), using shell screencap directly', this.takeScreenshotFailCount);
|
|
1297
|
+
throw new Error('Using shell screencap directly');
|
|
1289
1298
|
}
|
|
1290
1299
|
const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
|
|
1291
1300
|
if (validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) {
|
|
@@ -1314,12 +1323,21 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1314
1323
|
screenshotBuffer = await external_node_fs_default().promises.readFile(screenshotPath);
|
|
1315
1324
|
const validScreenshotBufferSize = this.options?.minScreenshotBufferSize ?? 10240;
|
|
1316
1325
|
if (!screenshotBuffer || validScreenshotBufferSize > 0 && screenshotBuffer.length < validScreenshotBufferSize) throw new Error(`Fallback screenshot validation failed: buffer size ${screenshotBuffer?.length || 0} bytes (minimum: ${validScreenshotBufferSize})`);
|
|
1317
|
-
if (!(0, img_namespaceObject.
|
|
1326
|
+
if (!(0, img_namespaceObject.isValidImageBuffer)(screenshotBuffer)) throw new Error('Fallback screenshot buffer has invalid PNG format');
|
|
1318
1327
|
debugDevice(`Fallback screenshot validated successfully: ${screenshotBuffer.length} bytes`);
|
|
1319
1328
|
} finally{
|
|
1320
|
-
|
|
1321
|
-
|
|
1329
|
+
const adbPath = adb.executable?.path ?? 'adb';
|
|
1330
|
+
const child = (0, external_node_child_process_namespaceObject.execFile)(adbPath, [
|
|
1331
|
+
'-s',
|
|
1332
|
+
this.deviceId,
|
|
1333
|
+
'shell',
|
|
1334
|
+
`rm ${androidScreenshotPath}`
|
|
1335
|
+
], {
|
|
1336
|
+
timeout: 3000
|
|
1337
|
+
}, (err)=>{
|
|
1338
|
+
if (err) debugDevice('Failed to delete remote screenshot: %s', err.message);
|
|
1322
1339
|
});
|
|
1340
|
+
child.unref();
|
|
1323
1341
|
}
|
|
1324
1342
|
}
|
|
1325
1343
|
if (!screenshotBuffer) throw new Error('Failed to capture screenshot: all methods failed');
|
|
@@ -1482,24 +1500,32 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1482
1500
|
if (!this.yadbPushed) {
|
|
1483
1501
|
const adb = await this.getAdb();
|
|
1484
1502
|
const androidPkgJson = (0, external_node_module_.createRequire)(__rslib_import_meta_url__).resolve('@aiscene/android/package.json');
|
|
1485
|
-
const
|
|
1486
|
-
await adb.push(
|
|
1503
|
+
const yadbBin = external_node_path_default().join(external_node_path_default().dirname(androidPkgJson), 'bin', 'yadb');
|
|
1504
|
+
await adb.push(yadbBin, '/data/local/tmp');
|
|
1487
1505
|
this.yadbPushed = true;
|
|
1488
1506
|
}
|
|
1489
1507
|
}
|
|
1490
1508
|
shouldUseYadbForText(text) {
|
|
1491
1509
|
const hasNonAscii = /[\x80-\uFFFF]/.test(text);
|
|
1492
1510
|
const hasFormatSpecifiers = /%[a-zA-Z]/.test(text);
|
|
1493
|
-
|
|
1511
|
+
const hasShellSpecialChars = /[\\`$]/.test(text);
|
|
1512
|
+
const hasBothQuotes = text.includes('"') && text.includes("'");
|
|
1513
|
+
return hasNonAscii || hasFormatSpecifiers || hasShellSpecialChars || hasBothQuotes;
|
|
1494
1514
|
}
|
|
1495
1515
|
async keyboardType(text, options) {
|
|
1496
1516
|
if (!text) return;
|
|
1497
1517
|
const adb = await this.getAdb();
|
|
1498
|
-
const shouldUseYadb = this.shouldUseYadbForText(text);
|
|
1499
1518
|
const IME_STRATEGY = (this.options?.imeStrategy || env_namespaceObject.globalConfigManager.getEnvConfigValue(env_namespaceObject.MIDSCENE_ANDROID_IME_STRATEGY)) ?? IME_STRATEGY_YADB_FOR_NON_ASCII;
|
|
1500
1519
|
const shouldAutoDismissKeyboard = options?.autoDismissKeyboard ?? this.options?.autoDismissKeyboard ?? true;
|
|
1501
|
-
|
|
1502
|
-
|
|
1520
|
+
const useYadb = IME_STRATEGY === IME_STRATEGY_ALWAYS_YADB || IME_STRATEGY === IME_STRATEGY_YADB_FOR_NON_ASCII && this.shouldUseYadbForText(text);
|
|
1521
|
+
if (useYadb) await this.execYadb(escapeForShell(text));
|
|
1522
|
+
else {
|
|
1523
|
+
const segments = text.split('\n');
|
|
1524
|
+
for(let i = 0; i < segments.length; i++){
|
|
1525
|
+
if (segments[i].length > 0) await adb.inputText(segments[i]);
|
|
1526
|
+
if (i < segments.length - 1) await adb.keyevent(66);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1503
1529
|
if (true === shouldAutoDismissKeyboard) await this.hideKeyboard(options);
|
|
1504
1530
|
}
|
|
1505
1531
|
normalizeKeyName(key) {
|
|
@@ -1547,12 +1573,12 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1547
1573
|
}
|
|
1548
1574
|
async mouseClick(x, y) {
|
|
1549
1575
|
const adb = await this.getAdb();
|
|
1550
|
-
const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
|
|
1576
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1551
1577
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} 150`);
|
|
1552
1578
|
}
|
|
1553
1579
|
async mouseDoubleClick(x, y) {
|
|
1554
1580
|
const adb = await this.getAdb();
|
|
1555
|
-
const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
|
|
1581
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1556
1582
|
const tapCommand = `input${this.getDisplayArg()} tap ${adjustedX} ${adjustedY}`;
|
|
1557
1583
|
await adb.shell(tapCommand);
|
|
1558
1584
|
await (0, core_utils_namespaceObject.sleep)(50);
|
|
@@ -1563,8 +1589,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1563
1589
|
}
|
|
1564
1590
|
async mouseDrag(from, to, duration) {
|
|
1565
1591
|
const adb = await this.getAdb();
|
|
1566
|
-
const { x: fromX, y: fromY } = this.adjustCoordinates(from.x, from.y);
|
|
1567
|
-
const { x: toX, y: toY } = this.adjustCoordinates(to.x, to.y);
|
|
1592
|
+
const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
|
|
1593
|
+
const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
|
|
1568
1594
|
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
1569
1595
|
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${swipeDuration}`);
|
|
1570
1596
|
}
|
|
@@ -1574,16 +1600,16 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1574
1600
|
const n = 4;
|
|
1575
1601
|
const startX = Math.round(deltaX < 0 ? width / n * (n - 1) : width / n);
|
|
1576
1602
|
const startY = Math.round(deltaY < 0 ? height / n * (n - 1) : height / n);
|
|
1577
|
-
const
|
|
1578
|
-
const
|
|
1579
|
-
const
|
|
1580
|
-
const
|
|
1603
|
+
const maxPositiveDeltaX = startX;
|
|
1604
|
+
const maxNegativeDeltaX = width - startX;
|
|
1605
|
+
const maxPositiveDeltaY = startY;
|
|
1606
|
+
const maxNegativeDeltaY = height - startY;
|
|
1581
1607
|
deltaX = Math.max(-maxNegativeDeltaX, Math.min(deltaX, maxPositiveDeltaX));
|
|
1582
1608
|
deltaY = Math.max(-maxNegativeDeltaY, Math.min(deltaY, maxPositiveDeltaY));
|
|
1583
1609
|
const endX = Math.round(startX - deltaX);
|
|
1584
1610
|
const endY = Math.round(startY - deltaY);
|
|
1585
|
-
const { x: adjustedStartX, y: adjustedStartY } = this.adjustCoordinates(startX, startY);
|
|
1586
|
-
const { x: adjustedEndX, y: adjustedEndY } = this.adjustCoordinates(endX, endY);
|
|
1611
|
+
const { x: adjustedStartX, y: adjustedStartY } = await this.adjustCoordinates(startX, startY);
|
|
1612
|
+
const { x: adjustedEndX, y: adjustedEndY } = await this.adjustCoordinates(endX, endY);
|
|
1587
1613
|
const adb = await this.getAdb();
|
|
1588
1614
|
const swipeDuration = duration ?? defaultNormalScrollDuration;
|
|
1589
1615
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedStartX} ${adjustedStartY} ${adjustedEndX} ${adjustedEndY} ${swipeDuration}`);
|
|
@@ -1594,6 +1620,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1594
1620
|
this.cachedPhysicalDisplayId = void 0;
|
|
1595
1621
|
this.cachedScreenSize = null;
|
|
1596
1622
|
this.cachedOrientation = null;
|
|
1623
|
+
this.scalingRatio = 1;
|
|
1597
1624
|
if (this.scrcpyAdapter) {
|
|
1598
1625
|
await this.scrcpyAdapter.disconnect();
|
|
1599
1626
|
this.scrcpyAdapter = null;
|
|
@@ -1633,7 +1660,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1633
1660
|
}
|
|
1634
1661
|
async longPress(x, y, duration = 2000) {
|
|
1635
1662
|
const adb = await this.getAdb();
|
|
1636
|
-
const { x: adjustedX, y: adjustedY } = this.adjustCoordinates(x, y);
|
|
1663
|
+
const { x: adjustedX, y: adjustedY } = await this.adjustCoordinates(x, y);
|
|
1637
1664
|
await adb.shell(`input${this.getDisplayArg()} swipe ${adjustedX} ${adjustedY} ${adjustedX} ${adjustedY} ${duration}`);
|
|
1638
1665
|
}
|
|
1639
1666
|
async pullDown(startPoint, distance, duration = 800) {
|
|
@@ -1655,8 +1682,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1655
1682
|
}
|
|
1656
1683
|
async pullDrag(from, to, duration) {
|
|
1657
1684
|
const adb = await this.getAdb();
|
|
1658
|
-
const { x: fromX, y: fromY } = this.adjustCoordinates(from.x, from.y);
|
|
1659
|
-
const { x: toX, y: toY } = this.adjustCoordinates(to.x, to.y);
|
|
1685
|
+
const { x: fromX, y: fromY } = await this.adjustCoordinates(from.x, from.y);
|
|
1686
|
+
const { x: toX, y: toY } = await this.adjustCoordinates(to.x, to.y);
|
|
1660
1687
|
await adb.shell(`input${this.getDisplayArg()} swipe ${fromX} ${fromY} ${toX} ${toY} ${duration}`);
|
|
1661
1688
|
}
|
|
1662
1689
|
async pullUp(startPoint, distance, duration = 600) {
|
|
@@ -1743,7 +1770,6 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1743
1770
|
device_define_property(this, "yadbPushed", false);
|
|
1744
1771
|
device_define_property(this, "devicePixelRatio", 1);
|
|
1745
1772
|
device_define_property(this, "devicePixelRatioInitialized", false);
|
|
1746
|
-
device_define_property(this, "scalingRatio", 1);
|
|
1747
1773
|
device_define_property(this, "adb", null);
|
|
1748
1774
|
device_define_property(this, "connectingAdb", null);
|
|
1749
1775
|
device_define_property(this, "destroyed", false);
|
|
@@ -1754,6 +1780,8 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1754
1780
|
device_define_property(this, "cachedPhysicalDisplayId", void 0);
|
|
1755
1781
|
device_define_property(this, "scrcpyAdapter", null);
|
|
1756
1782
|
device_define_property(this, "appNameMapping", {});
|
|
1783
|
+
device_define_property(this, "scalingRatio", 1);
|
|
1784
|
+
device_define_property(this, "takeScreenshotFailCount", 0);
|
|
1757
1785
|
device_define_property(this, "interfaceType", 'android');
|
|
1758
1786
|
device_define_property(this, "uri", void 0);
|
|
1759
1787
|
device_define_property(this, "options", void 0);
|
|
@@ -1763,6 +1791,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1763
1791
|
this.customActions = options?.customActions;
|
|
1764
1792
|
}
|
|
1765
1793
|
}
|
|
1794
|
+
device_define_property(AndroidDevice, "TAKE_SCREENSHOT_FAIL_THRESHOLD", 3);
|
|
1766
1795
|
const runAdbShellParamSchema = core_namespaceObject.z.object({
|
|
1767
1796
|
command: core_namespaceObject.z.string().describe('ADB shell command to execute')
|
|
1768
1797
|
});
|
|
@@ -1775,6 +1804,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1775
1804
|
description: 'Execute ADB shell command on Android device',
|
|
1776
1805
|
interfaceAlias: 'runAdbShell',
|
|
1777
1806
|
paramSchema: runAdbShellParamSchema,
|
|
1807
|
+
sample: {
|
|
1808
|
+
command: 'dumpsys window displays | grep -E "mCurrentFocus"'
|
|
1809
|
+
},
|
|
1778
1810
|
call: async (param)=>{
|
|
1779
1811
|
if (!param.command || '' === param.command.trim()) throw new Error('RunAdbShell requires a non-empty command parameter');
|
|
1780
1812
|
const adb = await device.getAdb();
|
|
@@ -1786,6 +1818,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1786
1818
|
description: 'Launch an Android app or URL',
|
|
1787
1819
|
interfaceAlias: 'launch',
|
|
1788
1820
|
paramSchema: launchParamSchema,
|
|
1821
|
+
sample: {
|
|
1822
|
+
uri: 'com.example.app'
|
|
1823
|
+
},
|
|
1789
1824
|
call: async (param)=>{
|
|
1790
1825
|
if (!param.uri || '' === param.uri.trim()) throw new Error('Launch requires a non-empty uri parameter');
|
|
1791
1826
|
await device.launch(param.uri);
|
|
@@ -1934,7 +1969,7 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
1934
1969
|
const tools = new AndroidMidsceneTools();
|
|
1935
1970
|
(0, cli_namespaceObject.runToolsCLI)(tools, 'midscene-android', {
|
|
1936
1971
|
stripPrefix: 'android_',
|
|
1937
|
-
version: "1.6.
|
|
1972
|
+
version: "1.6.8"
|
|
1938
1973
|
}).catch((e)=>{
|
|
1939
1974
|
if (!(e instanceof cli_namespaceObject.CLIError)) console.error(e);
|
|
1940
1975
|
process.exit(e instanceof cli_namespaceObject.CLIError ? e.exitCode : 1);
|