UNPKG

appium-adb

Version:

Android Debug Bridge interface

328 lines 11.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAdbWithCorrectAdbPath = getAdbWithCorrectAdbPath; exports.initAapt = initAapt; exports.initAapt2 = initAapt2; exports.initZipAlign = initZipAlign; exports.initBundletool = initBundletool; exports.getApiLevel = getApiLevel; exports.isDeviceConnected = isDeviceConnected; exports.clearTextField = clearTextField; exports.back = back; exports.goToHome = goToHome; exports.getAdbPath = getAdbPath; exports.restart = restart; exports.bugreport = bugreport; exports.screenrecord = screenrecord; exports.listFeatures = listFeatures; exports.isStreamedInstallSupported = isStreamedInstallSupported; exports.isIncrementalInstallSupported = isIncrementalInstallSupported; exports.takeScreenshot = takeScreenshot; const logger_1 = require("../logger"); const lodash_1 = __importDefault(require("lodash")); const support_1 = require("@appium/support"); const teen_process_1 = require("teen_process"); /** * Get the path to adb executable amd assign it * to this.executable.path and this.binaries.adb properties. * * @return ADB instance. */ async function getAdbWithCorrectAdbPath() { this.executable.path = await this.getSdkBinaryPath('adb'); return this; } /** * Get the full path to aapt tool and assign it to * this.binaries.aapt property */ async function initAapt() { await this.getSdkBinaryPath('aapt'); } /** * Get the full path to aapt2 tool and assign it to * this.binaries.aapt2 property */ async function initAapt2() { await this.getSdkBinaryPath('aapt2'); } /** * Get the full path to zipalign tool and assign it to * this.binaries.zipalign property */ async function initZipAlign() { await this.getSdkBinaryPath('zipalign'); } /** * Get the full path to bundletool binary and assign it to * this.binaries.bundletool property */ async function initBundletool() { try { const binaries = this.binaries; binaries.bundletool = await support_1.fs.which('bundletool.jar'); } catch { throw new Error('bundletool.jar binary is expected to be present in PATH. ' + 'Visit https://github.com/google/bundletool for more details.'); } } /** * Retrieve the API level of the device under test. * * @return The API level as integer number, for example 21 for * Android Lollipop. The result of this method is cached, so all the further * calls return the same value as the first one. */ async function getApiLevel() { if (!lodash_1.default.isInteger(this._apiLevel)) { try { const strOutput = await this.getDeviceProperty('ro.build.version.sdk'); let apiLevel = parseInt(strOutput.trim(), 10); // Workaround for preview/beta platform API level const charCodeQ = 'q'.charCodeAt(0); // 28 is the first API Level, where Android SDK started returning letters in response to getPlatformVersion const apiLevelDiff = apiLevel - 28; const codename = String.fromCharCode(charCodeQ + apiLevelDiff); if (apiLevelDiff >= 0 && (await this.getPlatformVersion()).toLowerCase() === codename) { logger_1.log.debug(`Release version is ${codename.toUpperCase()} but found API Level ${apiLevel}. Setting API Level to ${apiLevel + 1}`); apiLevel++; } this._apiLevel = apiLevel; logger_1.log.debug(`Device API level: ${this._apiLevel}`); if (isNaN(this._apiLevel)) { throw new Error(`The actual output '${strOutput}' cannot be converted to an integer`); } } catch (e) { throw new Error(`Error getting device API level. Original error: ${e.message}`); } } return this._apiLevel; } /** * Verify whether a device is connected. * * @return True if at least one device is visible to adb. */ async function isDeviceConnected() { const devices = await this.getConnectedDevices(); return devices.length > 0; } /** * Clear the active text field on the device under test by sending * special keyevents to it. * * @param length - The maximum length of the text in the field to be cleared. */ async function clearTextField(length = 100) { // assumes that the EditText field already has focus logger_1.log.debug(`Clearing up to ${length} characters`); if (length === 0) { return; } const args = ['input', 'keyevent']; for (let i = 0; i < length; i++) { // we cannot know where the cursor is in the text field, so delete both before // and after so that we get rid of everything // https://developer.android.com/reference/android/view/KeyEvent.html#KEYCODE_DEL // https://developer.android.com/reference/android/view/KeyEvent.html#KEYCODE_FORWARD_DEL args.push('67', '112'); } await this.shell(args); } /** * Send the special keycode to the device under test in order to emulate * Back button tap. */ async function back() { logger_1.log.debug('Pressing the BACK button'); await this.keyevent(4); } /** * Send the special keycode to the device under test in order to emulate * Home button tap. */ async function goToHome() { logger_1.log.debug('Pressing the HOME button'); await this.keyevent(3); } /** * @return the actual path to adb executable. */ function getAdbPath() { return this.executable.path; } /** * Restart the device under test using adb commands. * * @throws {Error} If start fails. */ async function restart() { try { await this.stopLogcat(); await this.restartAdb(); await this.waitForDevice(60); await this.startLogcat(this._logcatStartupParams); } catch (e) { const err = e; throw new Error(`Restart failed. Original error: ${err.message}`); } } /** * Retrieve the `adb bugreport` command output. This * operation may take up to several minutes. * * @param timeout - Command timeout in milliseconds * @returns Command stdout */ async function bugreport(timeout = 120000) { return await this.adbExec(['bugreport'], { timeout }); } /** * Initiate screenrecord utility on the device * * @param destination - Full path to the writable media file destination * on the device file system. * @param options - Screenrecord options * @returns screenrecord process, which can be then controlled by the client code */ function screenrecord(destination, options = {}) { const cmd = ['screenrecord']; const { videoSize, bitRate, timeLimit, bugReport } = options; if (support_1.util.hasValue(videoSize)) { cmd.push('--size', videoSize); } if (support_1.util.hasValue(timeLimit)) { cmd.push('--time-limit', `${timeLimit}`); } if (support_1.util.hasValue(bitRate)) { cmd.push('--bit-rate', `${bitRate}`); } if (bugReport) { cmd.push('--bugreport'); } cmd.push(destination); const fullCmd = [...this.executable.defaultArgs, 'shell', ...cmd]; logger_1.log.debug(`Building screenrecord process with the command line: adb ${support_1.util.quote(fullCmd)}`); return new teen_process_1.SubProcess(this.executable.path, fullCmd); } /** * Retrieves the list of features supported by the device under test * * @returns the list of supported feature names or an empty list. * An example adb command output: * ``` * cmd * ls_v2 * fixed_push_mkdir * shell_v2 * abb * stat_v2 * apex * abb_exec * remount_shell * fixed_push_symlink_timestamp * ``` * @throws {Error} if there was an error while retrieving the list */ async function listFeatures() { this._memoizedFeatures = this._memoizedFeatures || lodash_1.default.memoize(async () => await this.adbExec(['features']), () => this.curDeviceId); try { const memoizedFeatures = this._memoizedFeatures; if (!memoizedFeatures) { throw new Error('Memoized features function is not initialized'); } return (await memoizedFeatures()) .split(/\s+/) .map((x) => x.trim()) .filter(Boolean); } catch (e) { const err = e; if (lodash_1.default.includes(err.stderr, 'unknown command')) { return []; } throw err; } } /** * Checks the state of streamed install feature. * This feature allows to speed up apk installation * since it does not require the original apk to be pushed to * the device under test first, which also saves space. * Although, it is required that both the device under test * and the adb server have the mentioned functionality. * See https://github.com/aosp-mirror/platform_system_core/blob/master/adb/client/adb_install.cpp * for more details * * @returns `true` if the feature is supported by both adb and the * device under test */ async function isStreamedInstallSupported() { const proto = Object.getPrototypeOf(this); proto._helpOutput = proto._helpOutput || (await this.adbExec(['help'])); return proto._helpOutput.includes('--streaming') && (await this.listFeatures()).includes('cmd'); } /** * Checks whether incremental install feature is supported by ADB. * Read https://developer.android.com/preview/features#incremental * for more details on it. * * @returns `true` if the feature is supported by both adb and the * device under test */ async function isIncrementalInstallSupported() { const { binary } = await this.getVersion(); if (!binary) { return false; } return (support_1.util.compareVersions(`${binary.version}`, '>=', '30.0.1') && (await this.listFeatures()).includes('abb_exec')); } /** * Takes a screenshot of the given display or the default display. * * @param displayId A valid display identifier. If * no identifier is provided then the screenshot of the default display is returned. * Note that only recent Android APIs provide multi-screen support. * @returns PNG screenshot payload */ async function takeScreenshot(displayId) { const args = [...this.executable.defaultArgs, 'exec-out', 'screencap', '-p']; // @ts-ignore This validation works as expected const displayIdStr = isNaN(Number(displayId)) ? null : `${displayId}`; if (displayIdStr) { args.push('-d', displayIdStr); } const displayDescr = displayIdStr ? `display #${displayIdStr}` : 'default display'; let stdout; try { ({ stdout } = await (0, teen_process_1.exec)(this.executable.path, args, { encoding: 'binary', isBuffer: true })); } catch (e) { const err = e; let outputStr = ''; if (err.stderr) { const stderr = err.stderr; outputStr = typeof stderr === 'string' ? stderr : stderr.toString('utf-8'); } else if (err.stdout) { const stdout = err.stdout; outputStr = typeof stdout === 'string' ? stdout : stdout.toString('utf-8'); } throw new Error(`Screenshot of the ${displayDescr} failed. ` + `Code: '${err.code ?? 'unknown'}', output: '${outputStr}'`); } if (stdout.length === 0) { throw new Error(`Screenshot of the ${displayDescr} returned no data`); } return stdout; } //# sourceMappingURL=general-commands.js.map