appium-adb 14.0.2 → 14.0.4
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/CHANGELOG.md +12 -0
- package/build/lib/adb.d.ts +3 -11
- package/build/lib/adb.d.ts.map +1 -1
- package/build/lib/helpers.d.ts +29 -67
- package/build/lib/helpers.d.ts.map +1 -1
- package/build/lib/helpers.js +65 -98
- package/build/lib/helpers.js.map +1 -1
- package/build/lib/logger.d.ts +1 -1
- package/build/lib/logger.d.ts.map +1 -1
- package/build/lib/logger.js.map +1 -1
- package/build/lib/tools/system-calls.d.ts +157 -267
- package/build/lib/tools/system-calls.d.ts.map +1 -1
- package/build/lib/tools/system-calls.js +259 -312
- package/build/lib/tools/system-calls.js.map +1 -1
- package/lib/{helpers.js → helpers.ts} +113 -117
- package/lib/{logger.js → logger.ts} +1 -0
- package/lib/tools/{system-calls.js → system-calls.ts} +372 -407
- package/package.json +4 -4
|
@@ -77,7 +77,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
77
77
|
const logger_js_1 = require("../logger.js");
|
|
78
78
|
const bluebird_1 = __importDefault(require("bluebird"));
|
|
79
79
|
const support_1 = require("@appium/support");
|
|
80
|
-
const
|
|
80
|
+
const helpers_js_1 = require("../helpers.js");
|
|
81
81
|
const teen_process_1 = require("teen_process");
|
|
82
82
|
const asyncbox_1 = require("asyncbox");
|
|
83
83
|
const lodash_1 = __importDefault(require("lodash"));
|
|
@@ -103,22 +103,12 @@ const SDK_BINARY_ROOTS = [
|
|
|
103
103
|
const MIN_DELAY_ADB_API_LEVEL = 28;
|
|
104
104
|
const REQUIRED_SERVICES = ['activity', 'package', 'window'];
|
|
105
105
|
const MAX_SHELL_BUFFER_LENGTH = 1000;
|
|
106
|
-
|
|
107
|
-
* Retrieve full path to the given binary.
|
|
108
|
-
*
|
|
109
|
-
* @this {import('../adb.js').ADB}
|
|
110
|
-
* @param {string} binaryName - The name of the binary.
|
|
111
|
-
* @return {Promise<string>} Full path to the given binary including current SDK root.
|
|
112
|
-
*/
|
|
113
|
-
async function getSdkBinaryPath(binaryName) {
|
|
114
|
-
return await this.getBinaryFromSdkRoot(binaryName);
|
|
115
|
-
}
|
|
106
|
+
// Private methods (defined early as they're used by public methods)
|
|
116
107
|
/**
|
|
117
108
|
* Retrieve full binary name for the current operating system.
|
|
118
109
|
*
|
|
119
|
-
* @param
|
|
120
|
-
* @
|
|
121
|
-
* for example, 'android.bat' on Windows.
|
|
110
|
+
* @param binaryName - The name of the binary
|
|
111
|
+
* @returns The binary name with appropriate extension for the current OS
|
|
122
112
|
*/
|
|
123
113
|
function _getBinaryNameForOS(binaryName) {
|
|
124
114
|
if (!support_1.system.isWindows()) {
|
|
@@ -132,30 +122,61 @@ function _getBinaryNameForOS(binaryName) {
|
|
|
132
122
|
}
|
|
133
123
|
return binaryName;
|
|
134
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Returns the Android binaries locations
|
|
127
|
+
*
|
|
128
|
+
* @param sdkRoot - The Android SDK root directory path
|
|
129
|
+
* @param fullBinaryName - The full name of the binary (with extension)
|
|
130
|
+
* @returns Array of possible binary location paths
|
|
131
|
+
*/
|
|
132
|
+
function getSdkBinaryLocationCandidates(sdkRoot, fullBinaryName) {
|
|
133
|
+
return SDK_BINARY_ROOTS.map((x) => path_1.default.resolve(sdkRoot, ...(lodash_1.default.isArray(x) ? x : [x]), fullBinaryName));
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get the path to the openssl binary for the current operating system
|
|
137
|
+
*
|
|
138
|
+
* @returns The full path to the openssl binary
|
|
139
|
+
* @throws {Error} If openssl is not found in PATH
|
|
140
|
+
*/
|
|
141
|
+
async function getOpenSslForOs() {
|
|
142
|
+
const binaryName = `openssl${support_1.system.isWindows() ? '.exe' : ''}`;
|
|
143
|
+
try {
|
|
144
|
+
return await support_1.fs.which(binaryName);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
throw new Error('The openssl tool must be installed on the system and available on the path');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Public methods
|
|
151
|
+
/**
|
|
152
|
+
* Retrieve full path to the given binary.
|
|
153
|
+
*
|
|
154
|
+
* @param binaryName - The name of the binary
|
|
155
|
+
* @returns The full path to the binary
|
|
156
|
+
*/
|
|
157
|
+
async function getSdkBinaryPath(binaryName) {
|
|
158
|
+
return await this.getBinaryFromSdkRoot(binaryName);
|
|
159
|
+
}
|
|
135
160
|
exports.getBinaryNameForOS = lodash_1.default.memoize(_getBinaryNameForOS);
|
|
136
161
|
/**
|
|
137
162
|
* Retrieve full path to the given binary and caches it into `binaries`
|
|
138
163
|
* property of the current ADB instance.
|
|
139
164
|
*
|
|
140
|
-
* @
|
|
141
|
-
* @
|
|
142
|
-
* @
|
|
143
|
-
* to enumerate all the known locations where the binary
|
|
144
|
-
* might be located and stops the search as soon as the first
|
|
145
|
-
* match is found on the local file system.
|
|
146
|
-
* @throws {Error} If the binary with given name is not present at any
|
|
147
|
-
* of known locations or Android SDK is not installed on the
|
|
148
|
-
* local file system.
|
|
165
|
+
* @param binaryName - The name of the binary
|
|
166
|
+
* @returns The full path to the binary
|
|
167
|
+
* @throws {Error} If SDK root is not set or binary cannot be found
|
|
149
168
|
*/
|
|
150
169
|
async function getBinaryFromSdkRoot(binaryName) {
|
|
151
|
-
if (
|
|
152
|
-
return
|
|
170
|
+
if (this.binaries?.[binaryName]) {
|
|
171
|
+
return this.binaries[binaryName];
|
|
153
172
|
}
|
|
154
173
|
const fullBinaryName = this.getBinaryNameForOS(binaryName);
|
|
155
|
-
|
|
156
|
-
|
|
174
|
+
if (!this.sdkRoot) {
|
|
175
|
+
throw new Error('SDK root is not set');
|
|
176
|
+
}
|
|
177
|
+
const binaryLocs = getSdkBinaryLocationCandidates(this.sdkRoot, fullBinaryName);
|
|
157
178
|
// get subpaths for currently installed build tool directories
|
|
158
|
-
let buildToolsDirs = await (0, exports.getBuildToolsDirs)(
|
|
179
|
+
let buildToolsDirs = await (0, exports.getBuildToolsDirs)(this.sdkRoot);
|
|
159
180
|
if (this.buildToolsVersion) {
|
|
160
181
|
buildToolsDirs = buildToolsDirs
|
|
161
182
|
.filter((x) => path_1.default.basename(x) === this.buildToolsVersion);
|
|
@@ -184,38 +205,23 @@ async function getBinaryFromSdkRoot(binaryName) {
|
|
|
184
205
|
`installed at '${this.sdkRoot}'?`);
|
|
185
206
|
}
|
|
186
207
|
logger_js_1.log.info(`Using '${fullBinaryName}' from '${binaryLoc}'`);
|
|
187
|
-
|
|
208
|
+
if (!this.binaries) {
|
|
209
|
+
this.binaries = {};
|
|
210
|
+
}
|
|
211
|
+
this.binaries[binaryName] = binaryLoc;
|
|
188
212
|
return binaryLoc;
|
|
189
213
|
}
|
|
190
|
-
/**
|
|
191
|
-
* Returns the Android binaries locations
|
|
192
|
-
*
|
|
193
|
-
* @param {string} sdkRoot The path to Android SDK root.
|
|
194
|
-
* @param {string} fullBinaryName The name of full binary name.
|
|
195
|
-
* @return {string[]} The list of SDK_BINARY_ROOTS paths
|
|
196
|
-
* with sdkRoot and fullBinaryName.
|
|
197
|
-
*/
|
|
198
|
-
function getSdkBinaryLocationCandidates(sdkRoot, fullBinaryName) {
|
|
199
|
-
return SDK_BINARY_ROOTS.map((x) => path_1.default.resolve(sdkRoot, ...(lodash_1.default.isArray(x) ? x : [x]), fullBinaryName));
|
|
200
|
-
}
|
|
201
214
|
/**
|
|
202
215
|
* Retrieve full path to the given binary.
|
|
203
216
|
* This method does not have cache.
|
|
204
217
|
*
|
|
205
|
-
* @param
|
|
206
|
-
*
|
|
207
|
-
* @
|
|
208
|
-
* to enumerate all the known locations where the binary
|
|
209
|
-
* might be located and stops the search as soon as the first
|
|
210
|
-
* match is found on the local file system.
|
|
211
|
-
* e.g. '/Path/To/Android/sdk/platform-tools/adb'
|
|
212
|
-
* @throws {Error} If the binary with given name is not present at any
|
|
213
|
-
* of known locations or Android SDK is not installed on the
|
|
214
|
-
* local file system.
|
|
218
|
+
* @param binaryName - The name of the binary
|
|
219
|
+
* @returns The full path to the binary
|
|
220
|
+
* @throws {Error} If binary cannot be found in the Android SDK
|
|
215
221
|
*/
|
|
216
222
|
async function getAndroidBinaryPath(binaryName) {
|
|
217
223
|
const fullBinaryName = (0, exports.getBinaryNameForOS)(binaryName);
|
|
218
|
-
const sdkRoot = (0,
|
|
224
|
+
const sdkRoot = (0, helpers_js_1.getSdkRootFromEnv)();
|
|
219
225
|
const binaryLocs = getSdkBinaryLocationCandidates(sdkRoot ?? '', fullBinaryName);
|
|
220
226
|
for (const loc of binaryLocs) {
|
|
221
227
|
if (await support_1.fs.exists(loc)) {
|
|
@@ -228,21 +234,22 @@ async function getAndroidBinaryPath(binaryName) {
|
|
|
228
234
|
/**
|
|
229
235
|
* Retrieve full path to a binary file using the standard system lookup tool.
|
|
230
236
|
*
|
|
231
|
-
* @
|
|
232
|
-
* @
|
|
233
|
-
* @
|
|
234
|
-
* output.
|
|
235
|
-
* @throws {Error} If lookup tool returns non-zero return code.
|
|
237
|
+
* @param binaryName - The name of the binary
|
|
238
|
+
* @returns The full path to the binary
|
|
239
|
+
* @throws {Error} If binary cannot be found in PATH
|
|
236
240
|
*/
|
|
237
241
|
async function getBinaryFromPath(binaryName) {
|
|
238
|
-
if (
|
|
239
|
-
return
|
|
242
|
+
if (this.binaries?.[binaryName]) {
|
|
243
|
+
return this.binaries[binaryName];
|
|
240
244
|
}
|
|
241
245
|
const fullBinaryName = this.getBinaryNameForOS(binaryName);
|
|
242
246
|
try {
|
|
243
247
|
const binaryLoc = await support_1.fs.which(fullBinaryName);
|
|
244
248
|
logger_js_1.log.info(`Using '${fullBinaryName}' from '${binaryLoc}'`);
|
|
245
|
-
|
|
249
|
+
if (!this.binaries) {
|
|
250
|
+
this.binaries = {};
|
|
251
|
+
}
|
|
252
|
+
this.binaries[binaryName] = binaryLoc;
|
|
246
253
|
return binaryLoc;
|
|
247
254
|
}
|
|
248
255
|
catch {
|
|
@@ -253,11 +260,9 @@ async function getBinaryFromPath(binaryName) {
|
|
|
253
260
|
/**
|
|
254
261
|
* Retrieve the list of devices visible to adb.
|
|
255
262
|
*
|
|
256
|
-
* @
|
|
257
|
-
* @
|
|
258
|
-
* @
|
|
259
|
-
* no devices are connected.
|
|
260
|
-
* @throws {Error} If there was an error while listing devices.
|
|
263
|
+
* @param opts - Options for device retrieval
|
|
264
|
+
* @returns Array of connected devices
|
|
265
|
+
* @throws {Error} If adb devices command fails or returns unexpected output
|
|
261
266
|
*/
|
|
262
267
|
async function getConnectedDevices(opts = {}) {
|
|
263
268
|
logger_js_1.log.debug('Getting connected devices');
|
|
@@ -270,7 +275,8 @@ async function getConnectedDevices(opts = {}) {
|
|
|
270
275
|
({ stdout } = await (0, teen_process_1.exec)(this.executable.path, args));
|
|
271
276
|
}
|
|
272
277
|
catch (e) {
|
|
273
|
-
|
|
278
|
+
const error = e;
|
|
279
|
+
throw new Error(`Error while getting connected devices. Original error: ${error.message}`);
|
|
274
280
|
}
|
|
275
281
|
const listHeader = 'List of devices';
|
|
276
282
|
// expecting adb devices to return output as
|
|
@@ -282,7 +288,7 @@ async function getConnectedDevices(opts = {}) {
|
|
|
282
288
|
}
|
|
283
289
|
// slicing output we care about
|
|
284
290
|
stdout = stdout.slice(startingIndex);
|
|
285
|
-
|
|
291
|
+
const excludedLines = [listHeader, 'adb server', '* daemon'];
|
|
286
292
|
if (!this.allowOfflineDevices) {
|
|
287
293
|
excludedLines.push('offline');
|
|
288
294
|
}
|
|
@@ -315,16 +321,14 @@ async function getConnectedDevices(opts = {}) {
|
|
|
315
321
|
/**
|
|
316
322
|
* Retrieve the list of devices visible to adb within the given timeout.
|
|
317
323
|
*
|
|
318
|
-
* @
|
|
319
|
-
* @
|
|
320
|
-
*
|
|
321
|
-
* @return {Promise<import('./types').Device[]>} The list of connected devices.
|
|
322
|
-
* @throws {Error} If no connected devices can be detected within the given timeout.
|
|
324
|
+
* @param timeoutMs - Maximum time to wait for devices (default: 20000ms)
|
|
325
|
+
* @returns Array of connected devices
|
|
326
|
+
* @throws {Error} If no devices are found within the timeout period
|
|
323
327
|
*/
|
|
324
328
|
async function getDevicesWithRetry(timeoutMs = 20000) {
|
|
325
329
|
logger_js_1.log.debug('Trying to find connected Android devices');
|
|
326
330
|
try {
|
|
327
|
-
let devices;
|
|
331
|
+
let devices = [];
|
|
328
332
|
await (0, asyncbox_1.waitForCondition)(async () => {
|
|
329
333
|
try {
|
|
330
334
|
devices = await this.getConnectedDevices();
|
|
@@ -334,8 +338,9 @@ async function getDevicesWithRetry(timeoutMs = 20000) {
|
|
|
334
338
|
logger_js_1.log.debug('Could not find online devices');
|
|
335
339
|
}
|
|
336
340
|
catch (err) {
|
|
337
|
-
|
|
338
|
-
logger_js_1.log.
|
|
341
|
+
const error = err;
|
|
342
|
+
logger_js_1.log.debug(error.stack);
|
|
343
|
+
logger_js_1.log.warn(`Got an unexpected error while fetching connected devices list: ${error.message}`);
|
|
339
344
|
}
|
|
340
345
|
try {
|
|
341
346
|
await this.reconnect();
|
|
@@ -348,10 +353,11 @@ async function getDevicesWithRetry(timeoutMs = 20000) {
|
|
|
348
353
|
waitMs: timeoutMs,
|
|
349
354
|
intervalMs: 200,
|
|
350
355
|
});
|
|
351
|
-
return
|
|
356
|
+
return devices;
|
|
352
357
|
}
|
|
353
358
|
catch (e) {
|
|
354
|
-
|
|
359
|
+
const error = e;
|
|
360
|
+
if (/Condition unmet/.test(error.message)) {
|
|
355
361
|
throw new Error(`Could not find a connected Android device in ${timeoutMs}ms`);
|
|
356
362
|
}
|
|
357
363
|
else {
|
|
@@ -362,13 +368,8 @@ async function getDevicesWithRetry(timeoutMs = 20000) {
|
|
|
362
368
|
/**
|
|
363
369
|
* Kick current connection from host/device side and make it reconnect
|
|
364
370
|
*
|
|
365
|
-
* @
|
|
366
|
-
* @
|
|
367
|
-
* offline, device or null
|
|
368
|
-
* Providing `null` will cause reconnection to happen from the host side.
|
|
369
|
-
*
|
|
370
|
-
* @throws {Error} If either ADB version is too old and does not support this
|
|
371
|
-
* command or there was a failure during reconnect.
|
|
371
|
+
* @param target - The target to reconnect (default: 'offline')
|
|
372
|
+
* @throws {Error} If reconnect command fails
|
|
372
373
|
*/
|
|
373
374
|
async function reconnect(target = 'offline') {
|
|
374
375
|
logger_js_1.log.debug(`Reconnecting adb (target ${target})`);
|
|
@@ -380,13 +381,12 @@ async function reconnect(target = 'offline') {
|
|
|
380
381
|
await this.adbExec(args);
|
|
381
382
|
}
|
|
382
383
|
catch (e) {
|
|
383
|
-
|
|
384
|
+
const error = e;
|
|
385
|
+
throw new Error(`Cannot reconnect adb. Original error: ${error.stderr || error.message}`);
|
|
384
386
|
}
|
|
385
387
|
}
|
|
386
388
|
/**
|
|
387
389
|
* Restart adb server, unless _this.suppressKillServer_ property is true.
|
|
388
|
-
*
|
|
389
|
-
* @this {import('../adb.js').ADB}
|
|
390
390
|
*/
|
|
391
391
|
async function restartAdb() {
|
|
392
392
|
if (this.suppressKillServer) {
|
|
@@ -404,7 +404,6 @@ async function restartAdb() {
|
|
|
404
404
|
}
|
|
405
405
|
/**
|
|
406
406
|
* Kill adb server.
|
|
407
|
-
* @this {import('../adb.js').ADB}
|
|
408
407
|
*/
|
|
409
408
|
async function killServer() {
|
|
410
409
|
logger_js_1.log.debug(`Killing adb server on port '${this.adbPort}'`);
|
|
@@ -416,8 +415,7 @@ async function killServer() {
|
|
|
416
415
|
* Reset Telnet authentication token.
|
|
417
416
|
* @see {@link http://tools.android.com/recent/emulator2516releasenotes} for more details.
|
|
418
417
|
*
|
|
419
|
-
* @
|
|
420
|
-
* @returns {Promise<boolean>} If token reset was successful.
|
|
418
|
+
* @returns True if token was reset successfully, false otherwise
|
|
421
419
|
*/
|
|
422
420
|
exports.resetTelnetAuthToken = lodash_1.default.memoize(async function resetTelnetAuthToken() {
|
|
423
421
|
// The methods is used to remove telnet auth token
|
|
@@ -433,7 +431,8 @@ exports.resetTelnetAuthToken = lodash_1.default.memoize(async function resetTeln
|
|
|
433
431
|
await support_1.fs.writeFile(dstPath, '');
|
|
434
432
|
}
|
|
435
433
|
catch (e) {
|
|
436
|
-
|
|
434
|
+
const error = e;
|
|
435
|
+
logger_js_1.log.warn(`Error ${error.message} while resetting the content of ${dstPath}. Ignoring resetting of emulator's telnet authentication token`);
|
|
437
436
|
return false;
|
|
438
437
|
}
|
|
439
438
|
return true;
|
|
@@ -441,8 +440,8 @@ exports.resetTelnetAuthToken = lodash_1.default.memoize(async function resetTeln
|
|
|
441
440
|
/**
|
|
442
441
|
* Execute the given emulator command using _adb emu_ tool.
|
|
443
442
|
*
|
|
444
|
-
* @
|
|
445
|
-
* @
|
|
443
|
+
* @param cmd - Array of command arguments
|
|
444
|
+
* @throws {Error} If emulator is not connected or command execution fails
|
|
446
445
|
*/
|
|
447
446
|
async function adbExecEmu(cmd) {
|
|
448
447
|
await this.verifyEmulatorConnected();
|
|
@@ -450,38 +449,25 @@ async function adbExecEmu(cmd) {
|
|
|
450
449
|
await this.adbExec(['emu', ...cmd]);
|
|
451
450
|
}
|
|
452
451
|
let isExecLocked = false;
|
|
453
|
-
|
|
454
|
-
exports.EXEC_OUTPUT_FORMAT = Object.freeze({
|
|
452
|
+
exports.EXEC_OUTPUT_FORMAT = {
|
|
455
453
|
STDOUT: 'stdout',
|
|
456
454
|
FULL: 'full',
|
|
457
|
-
}
|
|
455
|
+
};
|
|
458
456
|
/**
|
|
459
457
|
* Execute the given adb command.
|
|
460
458
|
*
|
|
461
|
-
* @
|
|
462
|
-
* @
|
|
463
|
-
* @
|
|
464
|
-
*
|
|
465
|
-
* @param {TExecOpts} [opts] Additional options mapping. See
|
|
466
|
-
* {@link https://github.com/appium/node-teen_process}
|
|
467
|
-
* for more details.
|
|
468
|
-
* You can also set the additional `exclusive` param
|
|
469
|
-
* to `true` that assures no other parallel adb commands
|
|
470
|
-
* are going to be executed while the current one is running
|
|
471
|
-
* You can set the `outputFormat` param to `stdout` to receive just the stdout
|
|
472
|
-
* output (default) or `full` to receive the stdout and stderr response from a
|
|
473
|
-
* command with a zero exit code
|
|
474
|
-
* @return {Promise<TExecOpts extends import('./types').TFullOutputOption ? import('teen_process').TeenProcessExecResult : string>}
|
|
475
|
-
* Command's stdout or an object containing stdout and stderr.
|
|
476
|
-
* @throws {Error} If the command returned non-zero exit code.
|
|
459
|
+
* @param cmd - Command string or array of command arguments
|
|
460
|
+
* @param opts - Execution options
|
|
461
|
+
* @returns Command output (string or ExecResult depending on outputFormat)
|
|
462
|
+
* @throws {Error} If command execution fails or timeout is exceeded
|
|
477
463
|
*/
|
|
478
464
|
async function adbExec(cmd, opts) {
|
|
479
465
|
if (!cmd) {
|
|
480
466
|
throw new Error('You need to pass in a command to adbExec()');
|
|
481
467
|
}
|
|
482
|
-
const optsCopy = lodash_1.default.cloneDeep(opts
|
|
468
|
+
const optsCopy = lodash_1.default.cloneDeep(opts ?? {});
|
|
483
469
|
// setting default timeout for each command to prevent infinite wait.
|
|
484
|
-
optsCopy.timeout = optsCopy.timeout || this.adbExecTimeout ||
|
|
470
|
+
optsCopy.timeout = optsCopy.timeout || this.adbExecTimeout || helpers_js_1.DEFAULT_ADB_EXEC_TIMEOUT;
|
|
485
471
|
optsCopy.timeoutCapName = optsCopy.timeoutCapName || 'adbExecTimeout'; // For error message
|
|
486
472
|
const { outputFormat = this.EXEC_OUTPUT_FORMAT.STDOUT } = optsCopy;
|
|
487
473
|
cmd = lodash_1.default.isArray(cmd) ? cmd : [cmd];
|
|
@@ -491,37 +477,37 @@ async function adbExec(cmd, opts) {
|
|
|
491
477
|
const args = [...this.executable.defaultArgs, ...cmd];
|
|
492
478
|
logger_js_1.log.debug(`Running '${this.executable.path} ` +
|
|
493
479
|
(args.find((arg) => /\s+/.test(arg)) ? support_1.util.quote(args) : args.join(' ')) + `'`);
|
|
494
|
-
|
|
480
|
+
const { stdout: rawStdout, stderr } = await (0, teen_process_1.exec)(this.executable.path, args, optsCopy);
|
|
495
481
|
// sometimes ADB prints out weird stdout warnings that we don't want
|
|
496
482
|
// to include in any of the response data, so let's strip it out
|
|
497
|
-
stdout =
|
|
483
|
+
const stdout = rawStdout.replace(LINKER_WARNING_REGEXP, '').trim();
|
|
498
484
|
return outputFormat === this.EXEC_OUTPUT_FORMAT.FULL ? { stdout, stderr } : stdout;
|
|
499
485
|
}
|
|
500
486
|
catch (e) {
|
|
501
|
-
const
|
|
487
|
+
const error = e;
|
|
488
|
+
const errText = `${error.message}, ${error.stdout}, ${error.stderr}`;
|
|
502
489
|
if (ADB_RETRY_ERROR_PATTERNS.some((p) => p.test(errText))) {
|
|
503
490
|
logger_js_1.log.info(`Error sending command, reconnecting device and retrying: ${cmd}`);
|
|
504
|
-
await (0, asyncbox_1.sleep)(1000);
|
|
505
491
|
await this.getDevicesWithRetry();
|
|
506
492
|
// try again one time
|
|
507
|
-
if (adbRetried) {
|
|
493
|
+
if (!adbRetried) {
|
|
508
494
|
adbRetried = true;
|
|
509
495
|
return await execFunc();
|
|
510
496
|
}
|
|
511
497
|
}
|
|
512
|
-
if (
|
|
513
|
-
return
|
|
498
|
+
if (error.code === 0 && error.stdout) {
|
|
499
|
+
return error.stdout.replace(LINKER_WARNING_REGEXP, '').trim();
|
|
514
500
|
}
|
|
515
|
-
if (lodash_1.default.isNull(
|
|
516
|
-
|
|
501
|
+
if (lodash_1.default.isNull(error.code)) {
|
|
502
|
+
error.message = `Error executing adbExec. Original error: '${error.message}'. ` +
|
|
517
503
|
`Try to increase the ${optsCopy.timeout}ms adb execution timeout ` +
|
|
518
504
|
`represented by '${optsCopy.timeoutCapName}' capability`;
|
|
519
505
|
}
|
|
520
506
|
else {
|
|
521
|
-
|
|
522
|
-
`Command output: ${
|
|
507
|
+
error.message = `Error executing adbExec. Original error: '${error.message}'; ` +
|
|
508
|
+
`Command output: ${error.stderr || error.stdout || '<empty>'}`;
|
|
523
509
|
}
|
|
524
|
-
throw
|
|
510
|
+
throw error;
|
|
525
511
|
}
|
|
526
512
|
};
|
|
527
513
|
if (isExecLocked) {
|
|
@@ -547,17 +533,13 @@ async function adbExec(cmd, opts) {
|
|
|
547
533
|
/**
|
|
548
534
|
* Execute the given command using _adb shell_ prefix.
|
|
549
535
|
*
|
|
550
|
-
* @
|
|
551
|
-
* @
|
|
552
|
-
* @
|
|
553
|
-
*
|
|
554
|
-
* @param {TShellExecOpts} [opts] - Additional options mapping.
|
|
555
|
-
* @return {Promise<TShellExecOpts extends import('./types').TFullOutputOption ? import('teen_process').TeenProcessExecResult : string>}
|
|
556
|
-
* Command's stdout.
|
|
557
|
-
* @throws {Error} If the command returned non-zero exit code.
|
|
536
|
+
* @param cmd - Command string or array of command arguments
|
|
537
|
+
* @param opts - Execution options
|
|
538
|
+
* @returns Command output (string or ExecResult depending on outputFormat)
|
|
539
|
+
* @throws {Error} If command execution fails
|
|
558
540
|
*/
|
|
559
541
|
async function shell(cmd, opts) {
|
|
560
|
-
const { privileged, } = opts ??
|
|
542
|
+
const { privileged, } = opts ?? {};
|
|
561
543
|
const cmdArr = lodash_1.default.isArray(cmd) ? cmd : [cmd];
|
|
562
544
|
const fullCmd = ['shell'];
|
|
563
545
|
if (privileged) {
|
|
@@ -576,10 +558,10 @@ async function shell(cmd, opts) {
|
|
|
576
558
|
return await this.adbExec(fullCmd, opts);
|
|
577
559
|
}
|
|
578
560
|
/**
|
|
561
|
+
* Create a new ADB subprocess with the given arguments
|
|
579
562
|
*
|
|
580
|
-
* @
|
|
581
|
-
* @
|
|
582
|
-
* @returns {import('teen_process').SubProcess}
|
|
563
|
+
* @param args - Array of command arguments (default: empty array)
|
|
564
|
+
* @returns A SubProcess instance
|
|
583
565
|
*/
|
|
584
566
|
function createSubProcess(args = []) {
|
|
585
567
|
// add the default arguments
|
|
@@ -590,28 +572,27 @@ function createSubProcess(args = []) {
|
|
|
590
572
|
/**
|
|
591
573
|
* Retrieve the current adb port.
|
|
592
574
|
* @todo can probably deprecate this now that the logic is just to read this.adbPort
|
|
575
|
+
* @deprecated Use this.adbPort instead
|
|
593
576
|
*
|
|
594
|
-
* @
|
|
595
|
-
* @return {number} The current adb port number.
|
|
577
|
+
* @returns The ADB server port number
|
|
596
578
|
*/
|
|
597
579
|
function getAdbServerPort() {
|
|
598
|
-
return
|
|
580
|
+
return this.adbPort;
|
|
599
581
|
}
|
|
600
582
|
/**
|
|
601
|
-
* Retrieve the current emulator port from _adb
|
|
583
|
+
* Retrieve the current emulator port from _adb devices_ output.
|
|
602
584
|
*
|
|
603
|
-
* @
|
|
604
|
-
* @
|
|
605
|
-
* @throws {Error} If there are no connected devices.
|
|
585
|
+
* @returns The emulator port number
|
|
586
|
+
* @throws {Error} If no devices are connected or emulator port cannot be found
|
|
606
587
|
*/
|
|
607
588
|
async function getEmulatorPort() {
|
|
608
589
|
logger_js_1.log.debug('Getting running emulator port');
|
|
609
|
-
if (this.emulatorPort
|
|
610
|
-
return
|
|
590
|
+
if (!lodash_1.default.isNil(this.emulatorPort)) {
|
|
591
|
+
return this.emulatorPort;
|
|
611
592
|
}
|
|
612
593
|
try {
|
|
613
|
-
|
|
614
|
-
|
|
594
|
+
const devices = await this.getConnectedDevices();
|
|
595
|
+
const port = this.getPortFromEmulatorString(devices[0].udid);
|
|
615
596
|
if (port) {
|
|
616
597
|
return port;
|
|
617
598
|
}
|
|
@@ -620,38 +601,35 @@ async function getEmulatorPort() {
|
|
|
620
601
|
}
|
|
621
602
|
}
|
|
622
603
|
catch (e) {
|
|
623
|
-
|
|
604
|
+
const error = e;
|
|
605
|
+
throw new Error(`No devices connected. Original error: ${error.message}`);
|
|
624
606
|
}
|
|
625
607
|
}
|
|
626
608
|
/**
|
|
627
609
|
* Retrieve the current emulator port by parsing emulator name string.
|
|
628
610
|
*
|
|
629
|
-
* @
|
|
630
|
-
* @
|
|
631
|
-
* @return {number|false} Either the current emulator port or
|
|
632
|
-
* _false_ if port number cannot be parsed.
|
|
611
|
+
* @param emStr - The emulator string (e.g., 'emulator-5554')
|
|
612
|
+
* @returns The port number if found, false otherwise
|
|
633
613
|
*/
|
|
634
614
|
function getPortFromEmulatorString(emStr) {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
return false;
|
|
615
|
+
const portPattern = /emulator-(\d+)/;
|
|
616
|
+
const match = portPattern.exec(emStr);
|
|
617
|
+
return match ? parseInt(match[1], 10) : false;
|
|
640
618
|
}
|
|
641
619
|
/**
|
|
642
620
|
* Retrieve the list of currently connected emulators.
|
|
643
621
|
*
|
|
644
|
-
* @
|
|
645
|
-
* @
|
|
646
|
-
* @
|
|
622
|
+
* @param opts - Options for device retrieval
|
|
623
|
+
* @returns Array of connected emulator devices
|
|
624
|
+
* @throws {Error} If error occurs while getting emulators
|
|
647
625
|
*/
|
|
648
626
|
async function getConnectedEmulators(opts = {}) {
|
|
649
627
|
logger_js_1.log.debug('Getting connected emulators');
|
|
650
628
|
try {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
for (
|
|
654
|
-
|
|
629
|
+
const devices = await this.getConnectedDevices(opts);
|
|
630
|
+
const emulators = [];
|
|
631
|
+
for (const device of devices) {
|
|
632
|
+
const port = this.getPortFromEmulatorString(device.udid);
|
|
655
633
|
if (port) {
|
|
656
634
|
device.port = port;
|
|
657
635
|
emulators.push(device);
|
|
@@ -661,14 +639,14 @@ async function getConnectedEmulators(opts = {}) {
|
|
|
661
639
|
return emulators;
|
|
662
640
|
}
|
|
663
641
|
catch (e) {
|
|
664
|
-
|
|
642
|
+
const error = e;
|
|
643
|
+
throw new Error(`Error getting emulators. Original error: ${error.message}`);
|
|
665
644
|
}
|
|
666
645
|
}
|
|
667
646
|
/**
|
|
668
647
|
* Set _emulatorPort_ property of the current class.
|
|
669
648
|
*
|
|
670
|
-
* @
|
|
671
|
-
* @param {number} emPort - The emulator port to be set.
|
|
649
|
+
* @param emPort - The emulator port number
|
|
672
650
|
*/
|
|
673
651
|
function setEmulatorPort(emPort) {
|
|
674
652
|
this.emulatorPort = emPort;
|
|
@@ -676,13 +654,12 @@ function setEmulatorPort(emPort) {
|
|
|
676
654
|
/**
|
|
677
655
|
* Set the identifier of the current device (_this.curDeviceId_).
|
|
678
656
|
*
|
|
679
|
-
* @
|
|
680
|
-
* @param {string} deviceId - The device identifier.
|
|
657
|
+
* @param deviceId - The device identifier
|
|
681
658
|
*/
|
|
682
659
|
function setDeviceId(deviceId) {
|
|
683
660
|
logger_js_1.log.debug(`Setting device id to ${deviceId}`);
|
|
684
661
|
this.curDeviceId = deviceId;
|
|
685
|
-
|
|
662
|
+
const argsHasDevice = this.executable.defaultArgs.indexOf('-s');
|
|
686
663
|
if (argsHasDevice !== -1) {
|
|
687
664
|
// remove the old device id from the arguments
|
|
688
665
|
this.executable.defaultArgs.splice(argsHasDevice, 2);
|
|
@@ -690,10 +667,9 @@ function setDeviceId(deviceId) {
|
|
|
690
667
|
this.executable.defaultArgs.push('-s', deviceId);
|
|
691
668
|
}
|
|
692
669
|
/**
|
|
693
|
-
* Set the
|
|
670
|
+
* Set the current device object.
|
|
694
671
|
*
|
|
695
|
-
* @
|
|
696
|
-
* @param {import('./types').Device} deviceObj - The device object to be set.
|
|
672
|
+
* @param deviceObj - The device object containing udid and other properties
|
|
697
673
|
*/
|
|
698
674
|
function setDevice(deviceObj) {
|
|
699
675
|
const deviceId = deviceObj.udid;
|
|
@@ -709,9 +685,9 @@ function setDevice(deviceObj) {
|
|
|
709
685
|
* `deviceId` (only if AVD with a matching name is found)
|
|
710
686
|
* and `emulatorPort` instance properties.
|
|
711
687
|
*
|
|
712
|
-
* @
|
|
713
|
-
* @
|
|
714
|
-
* @
|
|
688
|
+
* @param avdName - The name of the AVD to find
|
|
689
|
+
* @returns The device object if found, null otherwise
|
|
690
|
+
* @throws {Error} If error occurs while getting AVD
|
|
715
691
|
*/
|
|
716
692
|
async function getRunningAVD(avdName) {
|
|
717
693
|
logger_js_1.log.debug(`Trying to find '${avdName}' emulator`);
|
|
@@ -736,47 +712,47 @@ async function getRunningAVD(avdName) {
|
|
|
736
712
|
return null;
|
|
737
713
|
}
|
|
738
714
|
catch (e) {
|
|
739
|
-
|
|
715
|
+
const error = e;
|
|
716
|
+
throw new Error(`Error getting AVD. Original error: ${error.message}`);
|
|
740
717
|
}
|
|
741
718
|
}
|
|
742
719
|
/**
|
|
743
|
-
* Get the object for the currently running emulator.
|
|
720
|
+
* Get the object for the currently running emulator with retry.
|
|
744
721
|
*
|
|
745
|
-
* @
|
|
746
|
-
* @param
|
|
747
|
-
* @
|
|
748
|
-
*
|
|
749
|
-
* is detected.
|
|
750
|
-
* @return {Promise<import('./types').Device|null>} Currently running emulator or _null_.
|
|
751
|
-
* @throws {Error} If no device has been detected within the timeout.
|
|
722
|
+
* @param avdName - The name of the AVD to find
|
|
723
|
+
* @param timeoutMs - Maximum time to wait (default: 20000ms)
|
|
724
|
+
* @returns The device object if found, null otherwise
|
|
725
|
+
* @throws {Error} If error occurs while getting AVD with retry
|
|
752
726
|
*/
|
|
753
727
|
async function getRunningAVDWithRetry(avdName, timeoutMs = 20000) {
|
|
754
728
|
try {
|
|
755
|
-
return
|
|
729
|
+
return await (0, asyncbox_1.waitForCondition)(async () => {
|
|
756
730
|
try {
|
|
757
731
|
return await this.getRunningAVD(avdName.replace('@', ''));
|
|
758
732
|
}
|
|
759
733
|
catch (e) {
|
|
760
|
-
|
|
734
|
+
const error = e;
|
|
735
|
+
logger_js_1.log.debug(error.message);
|
|
761
736
|
return false;
|
|
762
737
|
}
|
|
763
738
|
}, {
|
|
764
739
|
waitMs: timeoutMs,
|
|
765
740
|
intervalMs: 1000,
|
|
766
|
-
})
|
|
741
|
+
});
|
|
767
742
|
}
|
|
768
743
|
catch (e) {
|
|
769
|
-
|
|
744
|
+
const error = e;
|
|
745
|
+
throw new Error(`Error getting AVD with retry. Original error: ${error.message}`);
|
|
770
746
|
}
|
|
771
747
|
}
|
|
772
748
|
/**
|
|
773
749
|
* Shutdown all running emulators by killing their processes.
|
|
774
750
|
*
|
|
775
|
-
* @
|
|
776
|
-
* @throws {Error} If killing tool returned non-zero return code.
|
|
751
|
+
* @throws {Error} If error occurs while killing emulators
|
|
777
752
|
*/
|
|
778
753
|
async function killAllEmulators() {
|
|
779
|
-
let cmd
|
|
754
|
+
let cmd;
|
|
755
|
+
let args;
|
|
780
756
|
if (support_1.system.isWindows()) {
|
|
781
757
|
cmd = 'TASKKILL';
|
|
782
758
|
args = ['TASKKILL', '/IM', 'emulator.exe'];
|
|
@@ -789,20 +765,18 @@ async function killAllEmulators() {
|
|
|
789
765
|
await (0, teen_process_1.exec)(cmd, args);
|
|
790
766
|
}
|
|
791
767
|
catch (e) {
|
|
792
|
-
|
|
768
|
+
const error = e;
|
|
769
|
+
throw new Error(`Error killing emulators. Original error: ${error.message}`);
|
|
793
770
|
}
|
|
794
771
|
}
|
|
795
772
|
/**
|
|
796
773
|
* Kill emulator with the given name. No error
|
|
797
|
-
* is thrown
|
|
774
|
+
* is thrown if given avd does not exist/is not running.
|
|
798
775
|
*
|
|
799
|
-
* @
|
|
800
|
-
* @param
|
|
801
|
-
*
|
|
802
|
-
* @
|
|
803
|
-
* an exception about unsuccessful killing
|
|
804
|
-
* @return {Promise<boolean>} - True if the emulator was killed, false otherwise.
|
|
805
|
-
* @throws {Error} if there was a failure by killing the emulator
|
|
776
|
+
* @param avdName - The name of the AVD to kill (null to kill current AVD)
|
|
777
|
+
* @param timeout - Maximum time to wait for emulator to be killed (default: 60000ms)
|
|
778
|
+
* @returns True if emulator was killed, false if it was not running
|
|
779
|
+
* @throws {Error} If emulator is still running after timeout
|
|
806
780
|
*/
|
|
807
781
|
async function killEmulator(avdName = null, timeout = 60000) {
|
|
808
782
|
if (support_1.util.hasValue(avdName)) {
|
|
@@ -830,8 +804,9 @@ async function killEmulator(avdName = null, timeout = 60000) {
|
|
|
830
804
|
? !await this.getRunningAVD(avdName)
|
|
831
805
|
: !await this.isEmulatorConnected();
|
|
832
806
|
}
|
|
833
|
-
catch {
|
|
834
|
-
|
|
807
|
+
catch {
|
|
808
|
+
return false;
|
|
809
|
+
}
|
|
835
810
|
}, {
|
|
836
811
|
waitMs: timeout,
|
|
837
812
|
intervalMs: 2000,
|
|
@@ -846,23 +821,22 @@ async function killEmulator(avdName = null, timeout = 60000) {
|
|
|
846
821
|
/**
|
|
847
822
|
* Start an emulator with given parameters and wait until it is fully started.
|
|
848
823
|
*
|
|
849
|
-
* @
|
|
850
|
-
* @param
|
|
851
|
-
* @
|
|
852
|
-
* @
|
|
853
|
-
* @throws {Error} If the emulator fails to start within the given timeout.
|
|
824
|
+
* @param avdName - The name of the AVD to launch
|
|
825
|
+
* @param opts - Launch options
|
|
826
|
+
* @returns The SubProcess instance for the launched emulator
|
|
827
|
+
* @throws {Error} If emulator fails to launch or boot
|
|
854
828
|
*/
|
|
855
829
|
async function launchAVD(avdName, opts = {}) {
|
|
856
830
|
const { args = [], env = {}, language, country, launchTimeout = 60000, readyTimeout = 60000, retryTimes = 1, } = opts;
|
|
857
831
|
logger_js_1.log.debug(`Launching Emulator with AVD ${avdName}, launchTimeout ` +
|
|
858
832
|
`${launchTimeout}ms and readyTimeout ${readyTimeout}ms`);
|
|
859
833
|
const emulatorBinaryPath = await this.getSdkBinaryPath('emulator');
|
|
860
|
-
|
|
861
|
-
|
|
834
|
+
let processedAvdName = avdName;
|
|
835
|
+
if (processedAvdName.startsWith('@')) {
|
|
836
|
+
processedAvdName = processedAvdName.slice(1);
|
|
862
837
|
}
|
|
863
|
-
await this.checkAvdExist(
|
|
864
|
-
|
|
865
|
-
const launchArgs = ['-avd', avdName];
|
|
838
|
+
await this.checkAvdExist(processedAvdName);
|
|
839
|
+
const launchArgs = ['-avd', processedAvdName];
|
|
866
840
|
launchArgs.push(...(toAvdLocaleArgs(language ?? null, country ?? null)));
|
|
867
841
|
let isDelayAdbFeatureEnabled = false;
|
|
868
842
|
if (this.allowDelayAdb) {
|
|
@@ -870,7 +844,7 @@ async function launchAVD(avdName, opts = {}) {
|
|
|
870
844
|
if (revision && support_1.util.compareVersions(revision, '>=', '29.0.7')) {
|
|
871
845
|
// https://androidstudio.googleblog.com/2019/05/emulator-2907-canary.html
|
|
872
846
|
try {
|
|
873
|
-
const { target } = await this.getEmuImageProperties(
|
|
847
|
+
const { target } = await this.getEmuImageProperties(processedAvdName);
|
|
874
848
|
const apiMatch = /\d+/.exec(target);
|
|
875
849
|
// https://issuetracker.google.com/issues/142533355
|
|
876
850
|
if (apiMatch && parseInt(apiMatch[0], 10) >= MIN_DELAY_ADB_API_LEVEL) {
|
|
@@ -882,8 +856,9 @@ async function launchAVD(avdName, opts = {}) {
|
|
|
882
856
|
}
|
|
883
857
|
}
|
|
884
858
|
catch (e) {
|
|
859
|
+
const error = e;
|
|
885
860
|
logger_js_1.log.info(`The -delay-adb emulator startup detection feature will not be enabled. ` +
|
|
886
|
-
`Original error: ${
|
|
861
|
+
`Original error: ${error.message}`);
|
|
887
862
|
}
|
|
888
863
|
}
|
|
889
864
|
}
|
|
@@ -891,7 +866,7 @@ async function launchAVD(avdName, opts = {}) {
|
|
|
891
866
|
logger_js_1.log.info('The -delay-adb emulator startup detection feature has been explicitly disabled');
|
|
892
867
|
}
|
|
893
868
|
if (!lodash_1.default.isEmpty(args)) {
|
|
894
|
-
launchArgs.push(...(lodash_1.default.isArray(args) ? args :
|
|
869
|
+
launchArgs.push(...(lodash_1.default.isArray(args) ? args : support_1.util.shellParse(`${args}`)));
|
|
895
870
|
}
|
|
896
871
|
logger_js_1.log.debug(`Running '${emulatorBinaryPath}' with args: ${support_1.util.quote(launchArgs)}`);
|
|
897
872
|
if (!lodash_1.default.isEmpty(env)) {
|
|
@@ -905,9 +880,9 @@ async function launchAVD(avdName, opts = {}) {
|
|
|
905
880
|
proc.on(`line-${streamName}`, (line) => logger_js_1.log.debug(`[AVD OUTPUT] ${line}`));
|
|
906
881
|
}
|
|
907
882
|
proc.on('die', (code, signal) => {
|
|
908
|
-
logger_js_1.log.warn(`Emulator avd ${
|
|
883
|
+
logger_js_1.log.warn(`Emulator avd ${processedAvdName} exited with code ${code}${signal ? `, signal ${signal}` : ''}`);
|
|
909
884
|
});
|
|
910
|
-
await (0, asyncbox_1.retry)(retryTimes, async () => await this.getRunningAVDWithRetry(
|
|
885
|
+
await (0, asyncbox_1.retry)(retryTimes, async () => await this.getRunningAVDWithRetry(processedAvdName, launchTimeout));
|
|
911
886
|
// At this point we have deviceId already assigned
|
|
912
887
|
const timer = new support_1.timing.Timer().start();
|
|
913
888
|
if (isDelayAdbFeatureEnabled) {
|
|
@@ -915,7 +890,8 @@ async function launchAVD(avdName, opts = {}) {
|
|
|
915
890
|
await this.adbExec(['wait-for-device'], { timeout: readyTimeout });
|
|
916
891
|
}
|
|
917
892
|
catch (e) {
|
|
918
|
-
|
|
893
|
+
const error = e;
|
|
894
|
+
throw new Error(`'${processedAvdName}' Emulator has failed to boot: ${error.stderr || error.message}`);
|
|
919
895
|
}
|
|
920
896
|
}
|
|
921
897
|
await this.waitForEmulatorReady(Math.trunc(readyTimeout - timer.getDuration().asMilliSeconds));
|
|
@@ -924,9 +900,8 @@ async function launchAVD(avdName, opts = {}) {
|
|
|
924
900
|
/**
|
|
925
901
|
* Get the adb version. The result of this method is cached.
|
|
926
902
|
*
|
|
927
|
-
* @
|
|
928
|
-
* @
|
|
929
|
-
* @throws {Error} If it is not possible to parse adb binary version.
|
|
903
|
+
* @returns Version information object
|
|
904
|
+
* @throws {Error} If error occurs while getting adb version
|
|
930
905
|
*/
|
|
931
906
|
exports.getVersion = lodash_1.default.memoize(async function getVersion() {
|
|
932
907
|
let stdout;
|
|
@@ -934,20 +909,21 @@ exports.getVersion = lodash_1.default.memoize(async function getVersion() {
|
|
|
934
909
|
stdout = await this.adbExec('version');
|
|
935
910
|
}
|
|
936
911
|
catch (e) {
|
|
937
|
-
|
|
912
|
+
const error = e;
|
|
913
|
+
throw new Error(`Error getting adb version: ${error.stderr || error.message}`);
|
|
938
914
|
}
|
|
939
915
|
const result = {};
|
|
940
916
|
const binaryVersionMatch = BINARY_VERSION_PATTERN.exec(stdout);
|
|
941
917
|
if (binaryVersionMatch) {
|
|
942
918
|
result.binary = {
|
|
943
|
-
version: semver.coerce(binaryVersionMatch[1]),
|
|
919
|
+
version: semver.coerce(binaryVersionMatch[1])?.version || binaryVersionMatch[1],
|
|
944
920
|
build: parseInt(binaryVersionMatch[2], 10),
|
|
945
921
|
};
|
|
946
922
|
}
|
|
947
923
|
const bridgeVersionMatch = BRIDGE_VERSION_PATTERN.exec(stdout);
|
|
948
924
|
if (bridgeVersionMatch) {
|
|
949
925
|
result.bridge = {
|
|
950
|
-
version: semver.coerce(bridgeVersionMatch[1]),
|
|
926
|
+
version: semver.coerce(bridgeVersionMatch[1])?.version || bridgeVersionMatch[1],
|
|
951
927
|
};
|
|
952
928
|
}
|
|
953
929
|
return result;
|
|
@@ -955,14 +931,11 @@ exports.getVersion = lodash_1.default.memoize(async function getVersion() {
|
|
|
955
931
|
/**
|
|
956
932
|
* Check if the current emulator is ready to accept further commands (booting completed).
|
|
957
933
|
*
|
|
958
|
-
* @
|
|
959
|
-
* @
|
|
960
|
-
* @returns {Promise<void>}
|
|
961
|
-
* @throws {Error} If the emulator is not ready within the given timeout.
|
|
934
|
+
* @param timeoutMs - Maximum time to wait (default: 20000ms)
|
|
935
|
+
* @throws {Error} If emulator is not ready within the timeout period
|
|
962
936
|
*/
|
|
963
937
|
async function waitForEmulatorReady(timeoutMs = 20000) {
|
|
964
938
|
logger_js_1.log.debug(`Waiting up to ${timeoutMs}ms for the emulator to be ready`);
|
|
965
|
-
/** @type {RegExp[]} */
|
|
966
939
|
const requiredServicesRe = REQUIRED_SERVICES.map((name) => new RegExp(`\\b${name}:`));
|
|
967
940
|
let services;
|
|
968
941
|
const timer = new support_1.timing.Timer().start();
|
|
@@ -990,15 +963,17 @@ async function waitForEmulatorReady(timeoutMs = 20000) {
|
|
|
990
963
|
}
|
|
991
964
|
isBootCompleted = true;
|
|
992
965
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
966
|
+
const servicesOutput = await this.shell(['service', 'list']);
|
|
967
|
+
services = servicesOutput;
|
|
968
|
+
if (!servicesOutput || !requiredServicesRe.every((pattern) => pattern.test(servicesOutput))) {
|
|
969
|
+
logger_js_1.log.debug(`Running services: ${servicesOutput}`);
|
|
996
970
|
return false;
|
|
997
971
|
}
|
|
998
972
|
return true;
|
|
999
973
|
}
|
|
1000
974
|
catch (err) {
|
|
1001
|
-
|
|
975
|
+
const error = err;
|
|
976
|
+
logger_js_1.log.debug(`Intermediate error: ${error.message}`);
|
|
1002
977
|
return false;
|
|
1003
978
|
}
|
|
1004
979
|
}, {
|
|
@@ -1008,9 +983,10 @@ async function waitForEmulatorReady(timeoutMs = 20000) {
|
|
|
1008
983
|
}
|
|
1009
984
|
catch {
|
|
1010
985
|
let suffix = '';
|
|
1011
|
-
|
|
986
|
+
const servicesValue = services;
|
|
987
|
+
if (servicesValue) {
|
|
1012
988
|
const missingServices = lodash_1.default.zip(REQUIRED_SERVICES, requiredServicesRe)
|
|
1013
|
-
.filter(([, pattern]) => !
|
|
989
|
+
.filter(([, pattern]) => !pattern.test(servicesValue))
|
|
1014
990
|
.map(([name]) => name);
|
|
1015
991
|
suffix = ` (${missingServices} service${missingServices.length === 1 ? ' is' : 's are'} not running)`;
|
|
1016
992
|
}
|
|
@@ -1025,14 +1001,12 @@ async function waitForEmulatorReady(timeoutMs = 20000) {
|
|
|
1025
1001
|
/**
|
|
1026
1002
|
* Check if the current device is ready to accept further commands (booting completed).
|
|
1027
1003
|
*
|
|
1028
|
-
* @
|
|
1029
|
-
* @
|
|
1030
|
-
* @throws {Error} If the device is not ready within the given timeout.
|
|
1004
|
+
* @param appDeviceReadyTimeout - Timeout in seconds (default: 30)
|
|
1005
|
+
* @throws {Error} If device is not ready within the timeout period
|
|
1031
1006
|
*/
|
|
1032
1007
|
async function waitForDevice(appDeviceReadyTimeout = 30) {
|
|
1033
1008
|
const timeoutMs = appDeviceReadyTimeout * 1000;
|
|
1034
|
-
|
|
1035
|
-
let lastError = null;
|
|
1009
|
+
let lastErrorMessage = null;
|
|
1036
1010
|
try {
|
|
1037
1011
|
await (0, asyncbox_1.waitForCondition)(async () => {
|
|
1038
1012
|
try {
|
|
@@ -1041,7 +1015,8 @@ async function waitForDevice(appDeviceReadyTimeout = 30) {
|
|
|
1041
1015
|
return true;
|
|
1042
1016
|
}
|
|
1043
1017
|
catch (e) {
|
|
1044
|
-
|
|
1018
|
+
const error = e;
|
|
1019
|
+
lastErrorMessage = error.message;
|
|
1045
1020
|
try {
|
|
1046
1021
|
try {
|
|
1047
1022
|
await this.reconnect();
|
|
@@ -1051,7 +1026,9 @@ async function waitForDevice(appDeviceReadyTimeout = 30) {
|
|
|
1051
1026
|
}
|
|
1052
1027
|
await this.getConnectedDevices();
|
|
1053
1028
|
}
|
|
1054
|
-
catch {
|
|
1029
|
+
catch {
|
|
1030
|
+
// Ignore errors during reconnection
|
|
1031
|
+
}
|
|
1055
1032
|
return false;
|
|
1056
1033
|
}
|
|
1057
1034
|
}, {
|
|
@@ -1061,8 +1038,8 @@ async function waitForDevice(appDeviceReadyTimeout = 30) {
|
|
|
1061
1038
|
}
|
|
1062
1039
|
catch {
|
|
1063
1040
|
let suffix = '';
|
|
1064
|
-
if (
|
|
1065
|
-
suffix = ` Original error: ${
|
|
1041
|
+
if (lastErrorMessage) {
|
|
1042
|
+
suffix = ` Original error: ${lastErrorMessage}`;
|
|
1066
1043
|
}
|
|
1067
1044
|
throw new Error(`The device is not ready after ${appDeviceReadyTimeout}s.${suffix}`);
|
|
1068
1045
|
}
|
|
@@ -1070,9 +1047,8 @@ async function waitForDevice(appDeviceReadyTimeout = 30) {
|
|
|
1070
1047
|
/**
|
|
1071
1048
|
* Reboot the current device and wait until it is completed.
|
|
1072
1049
|
*
|
|
1073
|
-
* @
|
|
1074
|
-
* @
|
|
1075
|
-
* @throws {Error} If the device failed to reboot and number of retries is exceeded.
|
|
1050
|
+
* @param retries - Number of retry attempts (default: 90)
|
|
1051
|
+
* @throws {Error} If reboot fails or device is not ready after reboot
|
|
1076
1052
|
*/
|
|
1077
1053
|
async function reboot(retries = DEFAULT_ADB_REBOOT_RETRIES) {
|
|
1078
1054
|
// Get root access so we can run the next shell commands which require root access
|
|
@@ -1087,13 +1063,14 @@ async function reboot(retries = DEFAULT_ADB_REBOOT_RETRIES) {
|
|
|
1087
1063
|
await this.shell(['start']);
|
|
1088
1064
|
}
|
|
1089
1065
|
catch (e) {
|
|
1090
|
-
const
|
|
1066
|
+
const error = e;
|
|
1067
|
+
const { message } = error;
|
|
1091
1068
|
// provide a helpful error message if the reason reboot failed was because ADB couldn't gain root access
|
|
1092
1069
|
if (message.includes('must be root')) {
|
|
1093
1070
|
throw new Error(`Could not reboot device. Rebooting requires root access and ` +
|
|
1094
1071
|
`attempt to get root access on device failed with error: '${message}'`);
|
|
1095
1072
|
}
|
|
1096
|
-
throw
|
|
1073
|
+
throw error;
|
|
1097
1074
|
}
|
|
1098
1075
|
finally {
|
|
1099
1076
|
// Return root state to what it was before
|
|
@@ -1115,9 +1092,8 @@ async function reboot(retries = DEFAULT_ADB_REBOOT_RETRIES) {
|
|
|
1115
1092
|
/**
|
|
1116
1093
|
* Switch adb server root privileges.
|
|
1117
1094
|
*
|
|
1118
|
-
* @
|
|
1119
|
-
* @
|
|
1120
|
-
* @return {Promise<import('./types').RootResult>}
|
|
1095
|
+
* @param isElevated - True to enable root, false to disable
|
|
1096
|
+
* @returns Result object indicating success and whether device was already rooted
|
|
1121
1097
|
*/
|
|
1122
1098
|
async function changeUserPrivileges(isElevated) {
|
|
1123
1099
|
const cmd = isElevated ? 'root' : 'unroot';
|
|
@@ -1126,10 +1102,11 @@ async function changeUserPrivileges(isElevated) {
|
|
|
1126
1102
|
return await cmdFunc();
|
|
1127
1103
|
}
|
|
1128
1104
|
catch (err) {
|
|
1105
|
+
const error = err;
|
|
1129
1106
|
// Check the output of the stdErr to see if there's any clues that show that the device went offline
|
|
1130
1107
|
// and if it did go offline, restart ADB
|
|
1131
1108
|
if (['closed', 'device offline', 'timeout expired']
|
|
1132
|
-
.some((x) => (
|
|
1109
|
+
.some((x) => (error.stderr || '').toLowerCase().includes(x))) {
|
|
1133
1110
|
logger_js_1.log.warn(`Attempt to ${cmd} caused ADB to think the device went offline`);
|
|
1134
1111
|
try {
|
|
1135
1112
|
await this.reconnect();
|
|
@@ -1140,7 +1117,7 @@ async function changeUserPrivileges(isElevated) {
|
|
|
1140
1117
|
return await cmdFunc();
|
|
1141
1118
|
}
|
|
1142
1119
|
else {
|
|
1143
|
-
throw
|
|
1120
|
+
throw error;
|
|
1144
1121
|
}
|
|
1145
1122
|
}
|
|
1146
1123
|
};
|
|
@@ -1166,7 +1143,8 @@ async function changeUserPrivileges(isElevated) {
|
|
|
1166
1143
|
return { isSuccessful: true, wasAlreadyRooted };
|
|
1167
1144
|
}
|
|
1168
1145
|
catch (err) {
|
|
1169
|
-
const
|
|
1146
|
+
const error = err;
|
|
1147
|
+
const { stderr = '', message } = error;
|
|
1170
1148
|
logger_js_1.log.warn(`Unable to ${cmd} adb daemon. Original error: '${message}'. Stderr: '${stderr}'. Continuing.`);
|
|
1171
1149
|
return { isSuccessful: false, wasAlreadyRooted };
|
|
1172
1150
|
}
|
|
@@ -1174,8 +1152,7 @@ async function changeUserPrivileges(isElevated) {
|
|
|
1174
1152
|
/**
|
|
1175
1153
|
* Switch adb server to root mode
|
|
1176
1154
|
*
|
|
1177
|
-
* @
|
|
1178
|
-
* @return {Promise<import('./types').RootResult>}
|
|
1155
|
+
* @returns Result object indicating success and whether device was already rooted
|
|
1179
1156
|
*/
|
|
1180
1157
|
async function root() {
|
|
1181
1158
|
return await this.changeUserPrivileges(true);
|
|
@@ -1183,8 +1160,7 @@ async function root() {
|
|
|
1183
1160
|
/**
|
|
1184
1161
|
* Switch adb server to non-root mode.
|
|
1185
1162
|
*
|
|
1186
|
-
* @
|
|
1187
|
-
* @return {Promise<import('./types').RootResult>}
|
|
1163
|
+
* @returns Result object indicating success and whether device was already rooted
|
|
1188
1164
|
*/
|
|
1189
1165
|
async function unroot() {
|
|
1190
1166
|
return await this.changeUserPrivileges(false);
|
|
@@ -1192,10 +1168,7 @@ async function unroot() {
|
|
|
1192
1168
|
/**
|
|
1193
1169
|
* Checks whether the current user is root
|
|
1194
1170
|
*
|
|
1195
|
-
* @
|
|
1196
|
-
* @return {Promise<boolean>} True if the user is root
|
|
1197
|
-
* @throws {Error} if there was an error while identifying
|
|
1198
|
-
* the user.
|
|
1171
|
+
* @returns True if current user is root, false otherwise
|
|
1199
1172
|
*/
|
|
1200
1173
|
async function isRoot() {
|
|
1201
1174
|
return (await this.shell(['whoami'])).trim() === 'root';
|
|
@@ -1209,11 +1182,8 @@ async function isRoot() {
|
|
|
1209
1182
|
* Read https://github.com/appium/appium/issues/10964
|
|
1210
1183
|
* for more details on this topic
|
|
1211
1184
|
*
|
|
1212
|
-
* @
|
|
1213
|
-
* @
|
|
1214
|
-
* represented as a string or a buffer
|
|
1215
|
-
* @throws {Error} If openssl tool is not available on the destination system
|
|
1216
|
-
* or if there was an error while installing the certificate
|
|
1185
|
+
* @param cert - Certificate as Buffer or base64-encoded string
|
|
1186
|
+
* @throws {Error} If certificate installation fails
|
|
1217
1187
|
*/
|
|
1218
1188
|
async function installMitmCertificate(cert) {
|
|
1219
1189
|
const openSsl = await getOpenSslForOs();
|
|
@@ -1245,10 +1215,11 @@ async function installMitmCertificate(cert) {
|
|
|
1245
1215
|
await this.adbExec(['remount']);
|
|
1246
1216
|
}
|
|
1247
1217
|
catch (err) {
|
|
1218
|
+
const error = err;
|
|
1248
1219
|
throw new Error(`Cannot inject the custom certificate. ` +
|
|
1249
1220
|
`Is the certificate properly encoded into base64-string? ` +
|
|
1250
1221
|
`Do you have root permissions on the device? ` +
|
|
1251
|
-
`Original error: ${
|
|
1222
|
+
`Original error: ${error.message}`);
|
|
1252
1223
|
}
|
|
1253
1224
|
finally {
|
|
1254
1225
|
await support_1.fs.rimraf(tmpRoot);
|
|
@@ -1257,12 +1228,9 @@ async function installMitmCertificate(cert) {
|
|
|
1257
1228
|
/**
|
|
1258
1229
|
* Verifies if the given root certificate is already installed on the device.
|
|
1259
1230
|
*
|
|
1260
|
-
* @
|
|
1261
|
-
* @
|
|
1262
|
-
*
|
|
1263
|
-
* @throws {Error} If openssl tool is not available on the destination system
|
|
1264
|
-
* or if there was an error while checking the certificate
|
|
1265
|
-
* @returns {Promise<boolean>} true if the given certificate is already installed
|
|
1231
|
+
* @param cert - Certificate as Buffer or base64-encoded string
|
|
1232
|
+
* @returns True if certificate is installed, false otherwise
|
|
1233
|
+
* @throws {Error} If certificate hash cannot be retrieved
|
|
1266
1234
|
*/
|
|
1267
1235
|
async function isMitmCertificateInstalled(cert) {
|
|
1268
1236
|
const openSsl = await getOpenSslForOs();
|
|
@@ -1275,9 +1243,10 @@ async function isMitmCertificateInstalled(cert) {
|
|
|
1275
1243
|
certHash = stdout.trim();
|
|
1276
1244
|
}
|
|
1277
1245
|
catch (err) {
|
|
1246
|
+
const error = err;
|
|
1278
1247
|
throw new Error(`Cannot retrieve the certificate hash. ` +
|
|
1279
1248
|
`Is the certificate properly encoded into base64-string? ` +
|
|
1280
|
-
`Original error: ${
|
|
1249
|
+
`Original error: ${error.message}`);
|
|
1281
1250
|
}
|
|
1282
1251
|
finally {
|
|
1283
1252
|
await support_1.fs.rimraf(tmpRoot);
|
|
@@ -1292,18 +1261,12 @@ async function isMitmCertificateInstalled(cert) {
|
|
|
1292
1261
|
* there is a limit for a maximum length of a single adb command. that is why
|
|
1293
1262
|
* we need all this complicated logic.
|
|
1294
1263
|
*
|
|
1295
|
-
* @
|
|
1296
|
-
* @param
|
|
1297
|
-
*
|
|
1298
|
-
* of the function must be an array, where each item is a part of a single command.
|
|
1299
|
-
* The last item of the array could be ';'. If this is not a semicolon then it is going to
|
|
1300
|
-
* be added automatically.
|
|
1301
|
-
* @param {string[]} args Array of argument values to create chunks for
|
|
1302
|
-
* @throws {Error} If any of the chunks returns non-zero exit code after being executed
|
|
1264
|
+
* @param argTransformer - Function to transform each argument into command array
|
|
1265
|
+
* @param args - Array of arguments to process
|
|
1266
|
+
* @throws {Error} If argument transformer returns invalid result or command execution fails
|
|
1303
1267
|
*/
|
|
1304
1268
|
async function shellChunks(argTransformer, args) {
|
|
1305
1269
|
const commands = [];
|
|
1306
|
-
/** @type {string[]} */
|
|
1307
1270
|
let cmdChunk = [];
|
|
1308
1271
|
for (const arg of args) {
|
|
1309
1272
|
const nextCmd = argTransformer(arg);
|
|
@@ -1336,15 +1299,13 @@ async function shellChunks(argTransformer, args) {
|
|
|
1336
1299
|
throw lastError;
|
|
1337
1300
|
}
|
|
1338
1301
|
}
|
|
1339
|
-
// #region Private functions
|
|
1340
1302
|
/**
|
|
1341
1303
|
* Transforms the given language and country abbreviations
|
|
1342
1304
|
* to AVD arguments array
|
|
1343
1305
|
*
|
|
1344
|
-
* @param
|
|
1345
|
-
* @param
|
|
1346
|
-
* @returns
|
|
1347
|
-
* resulting array might be empty if both arguments are empty
|
|
1306
|
+
* @param language - Language code (e.g., 'en', 'fr')
|
|
1307
|
+
* @param country - Country code (e.g., 'US', 'FR')
|
|
1308
|
+
* @returns Array of AVD locale arguments
|
|
1348
1309
|
*/
|
|
1349
1310
|
function toAvdLocaleArgs(language, country) {
|
|
1350
1311
|
const result = [];
|
|
@@ -1373,7 +1334,8 @@ function toAvdLocaleArgs(language, country) {
|
|
|
1373
1334
|
* Retrieves full paths to all 'build-tools' subfolders under the particular
|
|
1374
1335
|
* SDK root folder
|
|
1375
1336
|
*
|
|
1376
|
-
* @
|
|
1337
|
+
* @param sdkRoot - The Android SDK root directory path
|
|
1338
|
+
* @returns Array of build-tools directory paths (newest first)
|
|
1377
1339
|
*/
|
|
1378
1340
|
exports.getBuildToolsDirs = lodash_1.default.memoize(async function getBuildToolsDirs(sdkRoot) {
|
|
1379
1341
|
let buildToolsDirs = await support_1.fs.glob('*/', {
|
|
@@ -1387,34 +1349,19 @@ exports.getBuildToolsDirs = lodash_1.default.memoize(async function getBuildTool
|
|
|
1387
1349
|
.map((pair) => pair[1]);
|
|
1388
1350
|
}
|
|
1389
1351
|
catch (err) {
|
|
1352
|
+
const error = err;
|
|
1390
1353
|
logger_js_1.log.warn(`Cannot sort build-tools folders ${JSON.stringify(buildToolsDirs.map((dir) => path_1.default.basename(dir)))} ` +
|
|
1391
1354
|
`by semantic version names.`);
|
|
1392
|
-
logger_js_1.log.warn(`Falling back to sorting by modification date. Original error: ${
|
|
1393
|
-
/** @type {[number, string][]} */
|
|
1355
|
+
logger_js_1.log.warn(`Falling back to sorting by modification date. Original error: ${error.message}`);
|
|
1394
1356
|
const pairs = await bluebird_1.default.map(buildToolsDirs, async (dir) => [(await support_1.fs.stat(dir)).mtime.valueOf(), dir]);
|
|
1395
1357
|
buildToolsDirs = pairs
|
|
1396
|
-
|
|
1397
|
-
.sort((a, b) => a[0] < b[0])
|
|
1358
|
+
.sort((a, b) => a[0] < b[0] ? 1 : -1)
|
|
1398
1359
|
.map((pair) => pair[1]);
|
|
1399
1360
|
}
|
|
1400
1361
|
logger_js_1.log.info(`Found ${buildToolsDirs.length} 'build-tools' folders under '${sdkRoot}' (newest first):`);
|
|
1401
|
-
for (
|
|
1362
|
+
for (const dir of buildToolsDirs) {
|
|
1402
1363
|
logger_js_1.log.info(` ${dir}`);
|
|
1403
1364
|
}
|
|
1404
1365
|
return buildToolsDirs;
|
|
1405
1366
|
});
|
|
1406
|
-
/**
|
|
1407
|
-
*
|
|
1408
|
-
* @returns {Promise<string>}
|
|
1409
|
-
*/
|
|
1410
|
-
async function getOpenSslForOs() {
|
|
1411
|
-
const binaryName = `openssl${support_1.system.isWindows() ? '.exe' : ''}`;
|
|
1412
|
-
try {
|
|
1413
|
-
return await support_1.fs.which(binaryName);
|
|
1414
|
-
}
|
|
1415
|
-
catch {
|
|
1416
|
-
throw new Error('The openssl tool must be installed on the system and available on the path');
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
// #endregion
|
|
1420
1367
|
//# sourceMappingURL=system-calls.js.map
|