appium-adb
Version:
Android Debug Bridge interface
338 lines • 12.2 kB
JavaScript
;
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_js_1 = require("../logger.js");
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.
*
* @this {import('../adb.js').ADB}
* @return {Promise<import('../adb.js').ADB>} 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
* @this {import('../adb.js').ADB}
*/
async function initAapt() {
await this.getSdkBinaryPath('aapt');
}
/**
* Get the full path to aapt2 tool and assign it to
* this.binaries.aapt2 property
* @this {import('../adb.js').ADB}
*/
async function initAapt2() {
await this.getSdkBinaryPath('aapt2');
}
/**
* Get the full path to zipalign tool and assign it to
* this.binaries.zipalign property
* @this {import('../adb.js').ADB}
*/
async function initZipAlign() {
await this.getSdkBinaryPath('zipalign');
}
/**
* Get the full path to bundletool binary and assign it to
* this.binaries.bundletool property
* @this {import('../adb.js').ADB}
*/
async function initBundletool() {
try {
( /** @type {import('./types').StringRecord} */(this.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.
*
* @this {import('../adb.js').ADB}
* @return {Promise<number>} 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_js_1.log.debug(`Release version is ${codename.toUpperCase()} but found API Level ${apiLevel}. Setting API Level to ${apiLevel + 1}`);
apiLevel++;
}
this._apiLevel = apiLevel;
logger_js_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: ${( /** @type {Error} */(e)).message}`);
}
}
return /** @type {number} */ (this._apiLevel);
}
/**
* Verify whether a device is connected.
*
* @this {import('../adb.js').ADB}
* @return {Promise<boolean>} True if at least one device is visible to adb.
*/
async function isDeviceConnected() {
let devices = await this.getConnectedDevices();
return devices.length > 0;
}
/**
* Clear the active text field on the device under test by sending
* special keyevents to it.
*
* @this {import('../adb.js').ADB}
* @param {number} [length=100] - 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_js_1.log.debug(`Clearing up to ${length} characters`);
if (length === 0) {
return;
}
let 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.
* @this {import('../adb.js').ADB}
*/
async function back() {
logger_js_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.
* @this {import('../adb.js').ADB}
*/
async function goToHome() {
logger_js_1.log.debug('Pressing the HOME button');
await this.keyevent(3);
}
/**
* @this {import('../adb.js').ADB}
* @return {string} the actual path to adb executable.
*/
function getAdbPath() {
return this.executable.path;
}
/**
* Restart the device under test using adb commands.
*
* @this {import('../adb.js').ADB}
* @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 = /** @type {Error} */ (e);
throw new Error(`Restart failed. Original error: ${err.message}`);
}
}
/**
* Retrieve the `adb bugreport` command output. This
* operation may take up to several minutes.
*
* @this {import('../adb.js').ADB}
* @param {number} [timeout=120000] - Command timeout in milliseconds
* @returns {Promise<string>} Command stdout
*/
async function bugreport(timeout = 120000) {
return await this.adbExec(['bugreport'], { timeout });
}
/**
* Initiate screenrecord utility on the device
*
* @this {import('../adb.js').ADB}
* @param {string} destination - Full path to the writable media file destination
* on the device file system.
* @param {import('./types').ScreenrecordOptions} [options={}]
* @returns {SubProcess} 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_js_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
*
* @this {import('../adb.js').ADB}
* @returns {Promise<string[]>} 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 {
return (await this._memoizedFeatures())
.split(/\s+/)
.map((x) => x.trim())
.filter(Boolean);
}
catch (e) {
const err = /** @type {import('teen_process').ExecError} */ (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
*
* @this {import('../adb.js').ADB}
* @returns {Promise<boolean>} `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.
*
* @this {import('../adb.js').ADB}
* @returns {Promise<boolean>} `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.
*
* @this {import('../adb.js').ADB}
* @param {number|string?} 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 {Promise<Buffer>} 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(displayId) ? null : `${displayId}`;
if (displayIdStr) {
args.push('-d', displayIdStr);
}
const displayDescr = displayIdStr ? 'default display' : `display #${displayIdStr}`;
let stdout;
try {
({ stdout } = await (0, teen_process_1.exec)(this.executable.path, args, { encoding: 'binary', isBuffer: true }));
}
catch (e) {
const err = /** @type {import('teen_process').ExecError} */ (e);
throw new Error(`Screenshot of the ${displayDescr} failed. ` +
// @ts-ignore The output is a buffer
`Code: '${err.code}', output: '${(err.stderr.length ? err.stderr : err.stdout).toString('utf-8')}'`);
}
if (stdout.length === 0) {
throw new Error(`Screenshot of the ${displayDescr} returned no data`);
}
return stdout;
}
//# sourceMappingURL=general-commands.js.map