@midscene/android 1.10.6-beta-20260716090839.0 → 1.10.6
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 +179 -43
- package/dist/es/index.mjs +185 -48
- package/dist/lib/cli.js +178 -42
- package/dist/lib/index.js +184 -47
- package/dist/types/index.d.ts +35 -8
- package/package.json +4 -4
package/dist/es/cli.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import { createDefaultMobileActions, defineAction } from "@midscene/core/device"
|
|
|
14
14
|
import { getTmpFile, sleep } from "@midscene/core/utils";
|
|
15
15
|
import { MIDSCENE_ADB_PATH, MIDSCENE_ADB_REMOTE_HOST, MIDSCENE_ADB_REMOTE_PORT, MIDSCENE_ANDROID_IME_STRATEGY, globalConfigManager } from "@midscene/shared/env";
|
|
16
16
|
import { createImgBase64ByFormat, validateScreenshotBuffer } from "@midscene/shared/img";
|
|
17
|
-
import { ADB
|
|
17
|
+
import { ADB, getSdkRootFromEnv } from "appium-adb";
|
|
18
18
|
var __webpack_modules__ = {
|
|
19
19
|
"./src/scrcpy-manager.ts" (__unused_rspack_module, __webpack_exports__, __webpack_require__) {
|
|
20
20
|
__webpack_require__.d(__webpack_exports__, {
|
|
@@ -52,6 +52,8 @@ var __webpack_modules__ = {
|
|
|
52
52
|
const KEYFRAME_POLL_INTERVAL_MS = 200;
|
|
53
53
|
const MAX_SCAN_BYTES = 1000;
|
|
54
54
|
const CONNECTION_WAIT_MS = 1000;
|
|
55
|
+
const MAX_SERVER_OUTPUT_LINES = 100;
|
|
56
|
+
const SERVER_OUTPUT_DRAIN_TIMEOUT_MS = 500;
|
|
55
57
|
const BUSY_LOOP_WINDOW_MS = 1000;
|
|
56
58
|
const BUSY_LOOP_MAX_READS = 500;
|
|
57
59
|
const BUSY_LOOP_COOLDOWN_MS = 50;
|
|
@@ -92,6 +94,8 @@ var __webpack_modules__ = {
|
|
|
92
94
|
if (this.scrcpyClient && this.videoStream) return void this.resetIdleTimer();
|
|
93
95
|
throw new Error('Scrcpy connection failed: another connection attempt did not complete in time');
|
|
94
96
|
}
|
|
97
|
+
const serverOutput = [];
|
|
98
|
+
let serverOutputTask = null;
|
|
95
99
|
try {
|
|
96
100
|
this.isConnecting = true;
|
|
97
101
|
debugScrcpy('Starting scrcpy connection...');
|
|
@@ -110,6 +114,7 @@ var __webpack_modules__ = {
|
|
|
110
114
|
videoCodecOptions: 'i-frame-interval=0,bitrate-mode=2'
|
|
111
115
|
});
|
|
112
116
|
this.scrcpyClient = await AdbScrcpyClient.start(this.adb, DefaultServerPath, scrcpyOptions);
|
|
117
|
+
serverOutputTask = this.collectServerOutput(this.scrcpyClient.output, serverOutput);
|
|
113
118
|
const videoStreamPromise = this.scrcpyClient.videoStream;
|
|
114
119
|
if (!videoStreamPromise) throw new Error('Scrcpy client did not provide video stream');
|
|
115
120
|
this.videoStream = await videoStreamPromise;
|
|
@@ -126,11 +131,50 @@ var __webpack_modules__ = {
|
|
|
126
131
|
} catch (error) {
|
|
127
132
|
debugScrcpy(`Failed to connect scrcpy: ${error}`);
|
|
128
133
|
await this.disconnect();
|
|
129
|
-
|
|
134
|
+
if (serverOutputTask) await Promise.race([
|
|
135
|
+
serverOutputTask,
|
|
136
|
+
new Promise((resolve)=>setTimeout(resolve, SERVER_OUTPUT_DRAIN_TIMEOUT_MS))
|
|
137
|
+
]);
|
|
138
|
+
throw this.createConnectionError(error, serverOutput);
|
|
130
139
|
} finally{
|
|
131
140
|
this.isConnecting = false;
|
|
132
141
|
}
|
|
133
142
|
}
|
|
143
|
+
async collectServerOutput(output, lines) {
|
|
144
|
+
const reader = output.getReader();
|
|
145
|
+
try {
|
|
146
|
+
while(true){
|
|
147
|
+
const { done, value } = await reader.read();
|
|
148
|
+
if (done) break;
|
|
149
|
+
lines.push(value);
|
|
150
|
+
if (lines.length > MAX_SERVER_OUTPUT_LINES) lines.splice(0, lines.length - MAX_SERVER_OUTPUT_LINES);
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
debugScrcpy(`Failed to read scrcpy server output: ${error}`);
|
|
154
|
+
} finally{
|
|
155
|
+
reader.releaseLock();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
createConnectionError(error, serverOutput) {
|
|
159
|
+
const errorOutput = this.getErrorOutput(error);
|
|
160
|
+
const output = [
|
|
161
|
+
...new Set([
|
|
162
|
+
...errorOutput,
|
|
163
|
+
...serverOutput
|
|
164
|
+
])
|
|
165
|
+
].filter((line)=>line.trim().length > 0);
|
|
166
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
167
|
+
const outputDetails = output.length > 0 ? `\nScrcpy server output:\n${output.join('\n')}` : '';
|
|
168
|
+
return new Error(`Failed to connect scrcpy: ${message}${outputDetails}`, {
|
|
169
|
+
cause: error
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
getErrorOutput(error) {
|
|
173
|
+
if ('object' != typeof error || null === error || !('output' in error)) return [];
|
|
174
|
+
const output = error.output;
|
|
175
|
+
if (!Array.isArray(output)) return [];
|
|
176
|
+
return output.filter((line)=>'string' == typeof line);
|
|
177
|
+
}
|
|
134
178
|
resolveServerBinPath() {
|
|
135
179
|
const androidPkgJson = (0, node_module__rspack_import_1.createRequire)(import.meta.url).resolve('@midscene/android/package.json');
|
|
136
180
|
return node_path__rspack_import_2["default"].join(node_path__rspack_import_2["default"].dirname(androidPkgJson), 'bin', 'scrcpy-server');
|
|
@@ -157,6 +201,7 @@ var __webpack_modules__ = {
|
|
|
157
201
|
let windowStart = Date.now();
|
|
158
202
|
let lastBusyWarn = 0;
|
|
159
203
|
let totalReads = 0;
|
|
204
|
+
let endReason = 'stream closed';
|
|
160
205
|
try {
|
|
161
206
|
while(true){
|
|
162
207
|
const { done, value } = await reader.read();
|
|
@@ -180,10 +225,11 @@ var __webpack_modules__ = {
|
|
|
180
225
|
this.processFrame(value);
|
|
181
226
|
}
|
|
182
227
|
} catch (error) {
|
|
228
|
+
endReason = 'stream error';
|
|
183
229
|
debugScrcpy(`Frame consumer error (total reads: ${totalReads}): ${error}`);
|
|
184
|
-
await this.disconnect();
|
|
185
230
|
}
|
|
186
|
-
|
|
231
|
+
if (this.streamReader === reader) await this.disconnect();
|
|
232
|
+
debugScrcpy(`Frame consumer loop ended (${endReason}, total reads: ${totalReads})`);
|
|
187
233
|
}
|
|
188
234
|
processFrame(packet) {
|
|
189
235
|
if ('configuration' === packet.type) {
|
|
@@ -417,8 +463,10 @@ var __webpack_modules__ = {
|
|
|
417
463
|
this.keyframeResolvers = [];
|
|
418
464
|
this.keyframeListeners.clear();
|
|
419
465
|
if (reader) try {
|
|
420
|
-
reader.cancel();
|
|
421
|
-
} catch
|
|
466
|
+
await reader.cancel();
|
|
467
|
+
} catch (error) {
|
|
468
|
+
debugScrcpy(`Error cancelling scrcpy stream reader: ${error}`);
|
|
469
|
+
}
|
|
422
470
|
if (client) try {
|
|
423
471
|
await client.close();
|
|
424
472
|
} catch (error) {
|
|
@@ -630,6 +678,35 @@ const defaultAppNameMapping = {
|
|
|
630
678
|
var external_node_fs_ = __webpack_require__("node:fs");
|
|
631
679
|
var external_node_module_ = __webpack_require__("node:module");
|
|
632
680
|
var external_node_path_ = __webpack_require__("node:path");
|
|
681
|
+
const warnAdb = (0, logger_.getDebug)('android:adb', {
|
|
682
|
+
console: true
|
|
683
|
+
});
|
|
684
|
+
async function adb_createAndroidAdb({ adbExecTimeout, deviceId, deviceOptions }) {
|
|
685
|
+
const androidAdbPath = deviceOptions?.androidAdbPath || globalConfigManager.getEnvConfigValue(MIDSCENE_ADB_PATH);
|
|
686
|
+
const remoteAdbHost = deviceOptions?.remoteAdbHost || globalConfigManager.getEnvConfigValue(MIDSCENE_ADB_REMOTE_HOST);
|
|
687
|
+
const remoteAdbPort = deviceOptions?.remoteAdbPort || globalConfigManager.getEnvConfigValue(MIDSCENE_ADB_REMOTE_PORT);
|
|
688
|
+
const adbOptions = {
|
|
689
|
+
udid: deviceId,
|
|
690
|
+
adbExecTimeout,
|
|
691
|
+
remoteAdbHost: remoteAdbHost || void 0,
|
|
692
|
+
remoteAdbPort: remoteAdbPort ? Number(remoteAdbPort) : void 0
|
|
693
|
+
};
|
|
694
|
+
if (androidAdbPath) return new ADB({
|
|
695
|
+
...adbOptions,
|
|
696
|
+
executable: {
|
|
697
|
+
path: androidAdbPath,
|
|
698
|
+
defaultArgs: []
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
const sdkRoot = getSdkRootFromEnv();
|
|
702
|
+
if (sdkRoot) try {
|
|
703
|
+
return await ADB.createADB(adbOptions);
|
|
704
|
+
} catch (error) {
|
|
705
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
706
|
+
warnAdb(`Unable to initialize adb from Android SDK at "${sdkRoot}", falling back to adb from PATH: ${message}`);
|
|
707
|
+
}
|
|
708
|
+
return new ADB(adbOptions);
|
|
709
|
+
}
|
|
633
710
|
var scrcpy_manager = __webpack_require__("./src/scrcpy-manager.ts");
|
|
634
711
|
function _define_property(obj, key, value) {
|
|
635
712
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
@@ -642,27 +719,56 @@ function _define_property(obj, key, value) {
|
|
|
642
719
|
return obj;
|
|
643
720
|
}
|
|
644
721
|
const debugAdapter = (0, logger_.getDebug)('android:scrcpy-adapter');
|
|
722
|
+
const SCRCPY_RETRY_COOLDOWN_MS = 5000;
|
|
723
|
+
const DEFAULT_ADB_SERVER_ENDPOINT = {
|
|
724
|
+
host: '127.0.0.1',
|
|
725
|
+
port: 5037
|
|
726
|
+
};
|
|
645
727
|
class ScrcpyDeviceAdapter {
|
|
646
728
|
isEnabled() {
|
|
647
|
-
if (this.
|
|
729
|
+
if (!this.isConfigured()) return false;
|
|
730
|
+
return null === this.retryAfter || Date.now() >= this.retryAfter;
|
|
731
|
+
}
|
|
732
|
+
getStatus() {
|
|
733
|
+
return {
|
|
734
|
+
enabled: this.isConfigured(),
|
|
735
|
+
connected: this.manager?.isConnected() ?? false,
|
|
736
|
+
lastError: this.lastError,
|
|
737
|
+
retryAfter: this.retryAfter
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
isConfigured() {
|
|
648
741
|
return this.scrcpyConfig?.enabled ?? scrcpy_manager.o.enabled;
|
|
649
742
|
}
|
|
650
743
|
async initialize(deviceInfo) {
|
|
651
744
|
try {
|
|
652
745
|
const manager = await this.ensureManager(deviceInfo);
|
|
653
746
|
await manager.ensureConnected();
|
|
747
|
+
this.clearFailure();
|
|
654
748
|
} catch (error) {
|
|
655
|
-
this.
|
|
749
|
+
this.recordFailure(error);
|
|
656
750
|
throw error;
|
|
657
751
|
}
|
|
658
752
|
}
|
|
753
|
+
recordFailure(error) {
|
|
754
|
+
this.lastError = error instanceof Error ? error.message : String(error);
|
|
755
|
+
this.retryAfter = Date.now() + SCRCPY_RETRY_COOLDOWN_MS;
|
|
756
|
+
}
|
|
757
|
+
clearFailure() {
|
|
758
|
+
this.lastError = null;
|
|
759
|
+
this.retryAfter = null;
|
|
760
|
+
}
|
|
761
|
+
ensureRetryReady() {
|
|
762
|
+
if (null === this.retryAfter || Date.now() >= this.retryAfter) return;
|
|
763
|
+
throw new Error(`scrcpy retry is cooling down until ${new Date(this.retryAfter).toISOString()}. Last error: ${this.lastError}`);
|
|
764
|
+
}
|
|
659
765
|
resolveConfig(deviceInfo) {
|
|
660
766
|
if (this.resolvedConfig) return this.resolvedConfig;
|
|
661
767
|
const config = this.scrcpyConfig;
|
|
662
768
|
const maxSize = config?.maxSize ?? scrcpy_manager.o.maxSize;
|
|
663
769
|
const videoBitRate = config?.videoBitRate ?? scrcpy_manager.o.videoBitRate;
|
|
664
770
|
this.resolvedConfig = {
|
|
665
|
-
enabled: this.
|
|
771
|
+
enabled: this.isConfigured(),
|
|
666
772
|
maxSize,
|
|
667
773
|
idleTimeoutMs: config?.idleTimeoutMs ?? scrcpy_manager.o.idleTimeoutMs,
|
|
668
774
|
videoBitRate
|
|
@@ -676,10 +782,8 @@ class ScrcpyDeviceAdapter {
|
|
|
676
782
|
const { Adb, AdbServerClient } = await import("@yume-chan/adb");
|
|
677
783
|
const { AdbServerNodeTcpConnector } = await import("@yume-chan/adb-server-node-tcp");
|
|
678
784
|
const { ScrcpyScreenshotManager: ScrcpyManager } = await Promise.resolve().then(__webpack_require__.bind(__webpack_require__, "./src/scrcpy-manager.ts"));
|
|
679
|
-
const
|
|
680
|
-
|
|
681
|
-
port: 5037
|
|
682
|
-
}));
|
|
785
|
+
const adbServerEndpoint = await this.resolveAdbServerEndpoint();
|
|
786
|
+
const adbClient = new AdbServerClient(new AdbServerNodeTcpConnector(adbServerEndpoint));
|
|
683
787
|
const adb = new Adb(await adbClient.createTransport({
|
|
684
788
|
serial: this.deviceId
|
|
685
789
|
}));
|
|
@@ -699,14 +803,28 @@ class ScrcpyDeviceAdapter {
|
|
|
699
803
|
}
|
|
700
804
|
}
|
|
701
805
|
async screenshotBase64(deviceInfo) {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
806
|
+
this.ensureRetryReady();
|
|
807
|
+
try {
|
|
808
|
+
const manager = await this.ensureManager(deviceInfo);
|
|
809
|
+
const screenshotBuffer = await manager.getScreenshotJpeg();
|
|
810
|
+
this.clearFailure();
|
|
811
|
+
return createImgBase64ByFormat('jpeg', screenshotBuffer.toString('base64'));
|
|
812
|
+
} catch (error) {
|
|
813
|
+
this.recordFailure(error);
|
|
814
|
+
throw error;
|
|
815
|
+
}
|
|
705
816
|
}
|
|
706
817
|
async subscribeKeyframes(deviceInfo, listener) {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
818
|
+
this.ensureRetryReady();
|
|
819
|
+
try {
|
|
820
|
+
const manager = await this.ensureManager(deviceInfo);
|
|
821
|
+
await manager.ensureConnected();
|
|
822
|
+
this.clearFailure();
|
|
823
|
+
return manager.subscribeKeyframes(listener);
|
|
824
|
+
} catch (error) {
|
|
825
|
+
this.recordFailure(error);
|
|
826
|
+
throw error;
|
|
827
|
+
}
|
|
710
828
|
}
|
|
711
829
|
getLatestRawKeyframe() {
|
|
712
830
|
return this.manager?.getLatestRawKeyframe() ?? null;
|
|
@@ -743,18 +861,23 @@ class ScrcpyDeviceAdapter {
|
|
|
743
861
|
this.manager = null;
|
|
744
862
|
}
|
|
745
863
|
this.resolvedConfig = null;
|
|
864
|
+
this.clearFailure();
|
|
746
865
|
}
|
|
747
|
-
constructor(deviceId, scrcpyConfig){
|
|
866
|
+
constructor(deviceId, scrcpyConfig, resolveAdbServerEndpoint = ()=>DEFAULT_ADB_SERVER_ENDPOINT){
|
|
748
867
|
_define_property(this, "deviceId", void 0);
|
|
749
868
|
_define_property(this, "scrcpyConfig", void 0);
|
|
869
|
+
_define_property(this, "resolveAdbServerEndpoint", void 0);
|
|
750
870
|
_define_property(this, "manager", void 0);
|
|
751
871
|
_define_property(this, "resolvedConfig", void 0);
|
|
752
|
-
_define_property(this, "
|
|
872
|
+
_define_property(this, "lastError", void 0);
|
|
873
|
+
_define_property(this, "retryAfter", void 0);
|
|
753
874
|
this.deviceId = deviceId;
|
|
754
875
|
this.scrcpyConfig = scrcpyConfig;
|
|
876
|
+
this.resolveAdbServerEndpoint = resolveAdbServerEndpoint;
|
|
755
877
|
this.manager = null;
|
|
756
878
|
this.resolvedConfig = null;
|
|
757
|
-
this.
|
|
879
|
+
this.lastError = null;
|
|
880
|
+
this.retryAfter = null;
|
|
758
881
|
}
|
|
759
882
|
}
|
|
760
883
|
function device_define_property(obj, key, value) {
|
|
@@ -881,7 +1004,7 @@ class AndroidDevice {
|
|
|
881
1004
|
console.log(`[midscene] Using scrcpy for screenshots (device: ${this.deviceId})`);
|
|
882
1005
|
} catch (error) {
|
|
883
1006
|
const msg = error instanceof Error ? error.message : String(error);
|
|
884
|
-
warnDevice(`[midscene] Scrcpy unavailable, using ADB fallback (device: ${this.deviceId}): ${msg}
|
|
1007
|
+
warnDevice(`[midscene] Scrcpy unavailable, using ADB fallback (device: ${this.deviceId}): ${msg}. Call retryScrcpy() to retry immediately.`);
|
|
885
1008
|
}
|
|
886
1009
|
return adb;
|
|
887
1010
|
}
|
|
@@ -893,18 +1016,10 @@ class AndroidDevice {
|
|
|
893
1016
|
let error = null;
|
|
894
1017
|
debugDevice(`Initializing ADB with device ID: ${this.deviceId}`);
|
|
895
1018
|
try {
|
|
896
|
-
|
|
897
|
-
const remoteAdbHost = this.options?.remoteAdbHost || globalConfigManager.getEnvConfigValue(MIDSCENE_ADB_REMOTE_HOST);
|
|
898
|
-
const remoteAdbPort = this.options?.remoteAdbPort || globalConfigManager.getEnvConfigValue(MIDSCENE_ADB_REMOTE_PORT);
|
|
899
|
-
this.adb = new external_appium_adb_ADB({
|
|
900
|
-
udid: this.deviceId,
|
|
1019
|
+
this.adb = await adb_createAndroidAdb({
|
|
901
1020
|
adbExecTimeout: 60000,
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
defaultArgs: []
|
|
905
|
-
} : void 0,
|
|
906
|
-
remoteAdbHost: remoteAdbHost || void 0,
|
|
907
|
-
remoteAdbPort: remoteAdbPort ? Number(remoteAdbPort) : void 0
|
|
1021
|
+
deviceId: this.deviceId,
|
|
1022
|
+
deviceOptions: this.options
|
|
908
1023
|
});
|
|
909
1024
|
const size = await this.getScreenSize();
|
|
910
1025
|
this.description = `
|
|
@@ -939,8 +1054,9 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
939
1054
|
} catch (error) {
|
|
940
1055
|
const methodName = String(prop);
|
|
941
1056
|
const deviceId = this.deviceId;
|
|
942
|
-
|
|
943
|
-
|
|
1057
|
+
const adbExecutable = target.executable.path;
|
|
1058
|
+
debugDevice(`ADB error with device ${deviceId} when calling ${methodName} (ADB executable: ${adbExecutable}): ${error}`);
|
|
1059
|
+
throw new Error(`ADB error with device ${deviceId} when calling ${methodName} (ADB executable: ${adbExecutable}), please check https://midscenejs.com/integrate-with-android.html#faq : ${error.message}`, {
|
|
944
1060
|
cause: error
|
|
945
1061
|
});
|
|
946
1062
|
}
|
|
@@ -948,8 +1064,24 @@ ${Object.keys(size).filter((key)=>size[key]).map((key)=>` ${key} size: ${size[k
|
|
|
948
1064
|
}
|
|
949
1065
|
});
|
|
950
1066
|
}
|
|
1067
|
+
getScrcpyStatus() {
|
|
1068
|
+
return this.getScrcpyAdapter().getStatus();
|
|
1069
|
+
}
|
|
1070
|
+
async retryScrcpy() {
|
|
1071
|
+
const adapter = this.getScrcpyAdapter();
|
|
1072
|
+
if (!adapter.getStatus().enabled) throw new Error('scrcpy is disabled in AndroidDevice options');
|
|
1073
|
+
const deviceInfo = await this.getDevicePhysicalInfo();
|
|
1074
|
+
await adapter.initialize(deviceInfo);
|
|
1075
|
+
return adapter.getStatus();
|
|
1076
|
+
}
|
|
951
1077
|
getScrcpyAdapter() {
|
|
952
|
-
if (!this.scrcpyAdapter) this.scrcpyAdapter = new ScrcpyDeviceAdapter(this.deviceId, this.options?.scrcpyConfig)
|
|
1078
|
+
if (!this.scrcpyAdapter) this.scrcpyAdapter = new ScrcpyDeviceAdapter(this.deviceId, this.options?.scrcpyConfig, async ()=>{
|
|
1079
|
+
const adb = await this.getAdb();
|
|
1080
|
+
return {
|
|
1081
|
+
host: adb.adbHost ?? '127.0.0.1',
|
|
1082
|
+
port: adb.adbPort ?? 5037
|
|
1083
|
+
};
|
|
1084
|
+
});
|
|
953
1085
|
return this.scrcpyAdapter;
|
|
954
1086
|
}
|
|
955
1087
|
async openScrcpyFrameSource() {
|
|
@@ -1981,17 +2113,21 @@ const createPlatformActions = (device)=>({
|
|
|
1981
2113
|
})
|
|
1982
2114
|
});
|
|
1983
2115
|
const debugUtils = (0, logger_.getDebug)('android:utils');
|
|
1984
|
-
async function getConnectedDevices() {
|
|
2116
|
+
async function getConnectedDevices(deviceOptions) {
|
|
2117
|
+
let adbExecutable;
|
|
1985
2118
|
try {
|
|
1986
|
-
const adb = await
|
|
1987
|
-
adbExecTimeout: 60000
|
|
2119
|
+
const adb = await adb_createAndroidAdb({
|
|
2120
|
+
adbExecTimeout: 60000,
|
|
2121
|
+
deviceOptions
|
|
1988
2122
|
});
|
|
2123
|
+
adbExecutable = adb.executable.path;
|
|
1989
2124
|
const devices = await adb.getConnectedDevices();
|
|
1990
2125
|
debugUtils(`Found ${devices.length} connected devices: `, devices);
|
|
1991
2126
|
return devices;
|
|
1992
2127
|
} catch (error) {
|
|
1993
2128
|
console.error('Failed to get device list:', error);
|
|
1994
|
-
|
|
2129
|
+
const adbExecutableContext = adbExecutable ? ` (ADB executable: ${adbExecutable})` : '';
|
|
2130
|
+
throw new Error(`Unable to get connected Android device list${adbExecutableContext}, please check https://midscenejs.com/integrate-with-android.html#faq : ${error.message}`, {
|
|
1995
2131
|
cause: error
|
|
1996
2132
|
});
|
|
1997
2133
|
}
|
|
@@ -2047,7 +2183,7 @@ class AndroidAgent extends Agent {
|
|
|
2047
2183
|
}
|
|
2048
2184
|
async function agentFromAdbDevice(deviceId, opts) {
|
|
2049
2185
|
if (!deviceId) {
|
|
2050
|
-
const devices = await getConnectedDevices();
|
|
2186
|
+
const devices = await getConnectedDevices(opts);
|
|
2051
2187
|
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.');
|
|
2052
2188
|
deviceId = devices[0].udid;
|
|
2053
2189
|
debugAgent('deviceId not specified, will use the first device (id = %s)', deviceId);
|
|
@@ -2176,7 +2312,7 @@ class AndroidMidsceneTools extends BaseMidsceneTools {
|
|
|
2176
2312
|
const tools = new AndroidMidsceneTools();
|
|
2177
2313
|
runToolsCLI(tools, 'midscene-android', {
|
|
2178
2314
|
stripPrefix: 'android_',
|
|
2179
|
-
version: "1.10.6
|
|
2315
|
+
version: "1.10.6",
|
|
2180
2316
|
extraCommands: createReportCliCommands()
|
|
2181
2317
|
}).catch((e)=>{
|
|
2182
2318
|
process.exit(reportCLIError(e));
|