UNPKG

appium-adb

Version:

Android Debug Bridge interface

252 lines 9.88 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.execBundletool = execBundletool; exports.getDeviceSpec = getDeviceSpec; exports.installMultipleApks = installMultipleApks; exports.installApks = installApks; exports.extractBaseApk = extractBaseApk; exports.extractLanguageApk = extractLanguageApk; exports.isTestPackageOnlyError = isTestPackageOnlyError; const teen_process_1 = require("teen_process"); const logger_1 = require("../logger"); const node_path_1 = __importDefault(require("node:path")); const lodash_1 = __importDefault(require("lodash")); const support_1 = require("@appium/support"); const lru_cache_1 = require("lru-cache"); const helpers_1 = require("../helpers"); const async_lock_1 = __importDefault(require("async-lock")); const bluebird_1 = __importDefault(require("bluebird")); const BASE_APK = 'base-master.apk'; const LANGUAGE_APK = (lang) => `base-${lang}.apk`; const APKS_CACHE = new lru_cache_1.LRUCache({ max: 10, dispose: (extractedFilesRoot) => support_1.fs.rimraf(extractedFilesRoot), }); const APKS_CACHE_GUARD = new async_lock_1.default(); const BUNDLETOOL_TIMEOUT_MS = 4 * 60 * 1000; const APKS_INSTALL_TIMEOUT = helpers_1.APK_INSTALL_TIMEOUT * 2; process.on('exit', () => { if (!APKS_CACHE.size) { return; } const paths = [...APKS_CACHE.values()]; logger_1.log.debug(`Performing cleanup of ${paths.length} cached .apks ` + support_1.util.pluralize('package', paths.length)); for (const appPath of paths) { try { // Asynchronous calls are not supported in onExit handler support_1.fs.rimrafSync(appPath); } catch (e) { logger_1.log.warn(e.message); } } }); /** * Extracts the particular apks package into a temporary folder, * finds and returns the full path to the file contained in this apk. * The resulting temporary path, where the .apks file has been extracted, * will be stored into the internal LRU cache for better performance. * * @param apks - The full path to the .apks file * @param dstPath - The relative path to the destination file, * which is going to be extracted, where each path component is an array item * @returns Full path to the extracted file * @throws {Error} If the requested item does not exist in the extracted archive or the provides * apks file is not a valid bundle */ async function extractFromApks(apks, dstPath) { const normalizedDstPath = lodash_1.default.isArray(dstPath) ? dstPath : [dstPath]; return await APKS_CACHE_GUARD.acquire(apks, async () => { // It might be that the original file has been replaced, // so we need to keep the hash sums instead of the actual file paths // as caching keys const apksHash = await support_1.fs.hash(apks); logger_1.log.debug(`Calculated '${apks}' hash: ${apksHash}`); if (APKS_CACHE.has(apksHash)) { const cachedRoot = APKS_CACHE.get(apksHash); if (cachedRoot) { const resultPath = node_path_1.default.resolve(cachedRoot, ...normalizedDstPath); if (await support_1.fs.exists(resultPath)) { return resultPath; } } APKS_CACHE.delete(apksHash); } const tmpRoot = await support_1.tempDir.openDir(); logger_1.log.debug(`Unpacking application bundle at '${apks}' to '${tmpRoot}'`); await (0, helpers_1.unzipFile)(apks, tmpRoot); const resultPath = node_path_1.default.resolve(tmpRoot, ...normalizedDstPath); if (!(await support_1.fs.exists(resultPath))) { throw new Error(`${normalizedDstPath.join(node_path_1.default.sep)} cannot be found in '${apks}' bundle. ` + `Does the archive contain a valid application bundle?`); } APKS_CACHE.set(apksHash, tmpRoot); return resultPath; }); } /** * Executes bundletool utility with given arguments and returns the actual stdout * * @param args - the list of bundletool arguments * @param errorMsg - The customized error message string * @returns the actual command stdout * @throws {Error} If bundletool jar does not exist in PATH or there was an error while * executing it */ async function execBundletool(args, errorMsg) { await this.initBundletool(); const binaries = this.binaries; args = ['-jar', binaries.bundletool, ...args]; const env = process.env; if (this.adbPort) { env.ANDROID_ADB_SERVER_PORT = `${this.adbPort}`; } if (this.adbHost) { env.ANDROID_ADB_SERVER_HOST = this.adbHost; } logger_1.log.debug(`Executing bundletool with arguments: ${JSON.stringify(args)}`); let stdout; try { ({ stdout } = await (0, teen_process_1.exec)(await (0, helpers_1.getJavaForOs)(), args, { env, timeout: BUNDLETOOL_TIMEOUT_MS, })); logger_1.log.debug(`Command stdout: ${lodash_1.default.truncate(stdout, { length: 300 })}`); return stdout; } catch (e) { const err = e; if (err.stdout) { logger_1.log.debug(`Command stdout: ${err.stdout}`); } if (err.stderr) { logger_1.log.debug(`Command stderr: ${err.stderr}`); } throw new Error(`${errorMsg}. Original error: ${err.message}`); } } /** * * @param specLocation - The full path to the generated device spec location * @returns The same `specLocation` value * @throws {Error} If it is not possible to retrieve the spec for the current device */ async function getDeviceSpec(specLocation) { const args = [ 'get-device-spec', '--adb', this.executable.path, '--device-id', this.curDeviceId, '--output', specLocation, ]; logger_1.log.debug(`Getting the spec for the device '${this.curDeviceId}'`); await this.execBundletool(args, 'Cannot retrieve the device spec'); return specLocation; } /** * Installs the given apks into the device under test * * @param apkPathsToInstall - The full paths to install apks * @param options - Installation options */ async function installMultipleApks(apkPathsToInstall, options = {}) { const installArgs = (0, helpers_1.buildInstallArgs)(await this.getApiLevel(), options); return await this.adbExec(['install-multiple', ...installArgs, ...apkPathsToInstall], { // @ts-ignore This validation works timeout: isNaN(Number(options.timeout)) ? undefined : options.timeout, timeoutCapName: options.timeoutCapName, }); } /** * Installs the given .apks package into the device under test * * @param apks - The full path to the .apks file * @param options - Installation options * @throws {Error} If the .apks bundle cannot be installed */ async function installApks(apks, options = {}) { const { grantPermissions, allowTestPackages, timeout } = options; const args = [ 'install-apks', '--adb', this.executable.path, '--apks', apks, '--timeout-millis', `${timeout || APKS_INSTALL_TIMEOUT}`, '--device-id', this.curDeviceId, ]; if (allowTestPackages) { args.push('--allow-test-only'); } const tasks = [ this.execBundletool(args, `Cannot install '${node_path_1.default.basename(apks)}' to the device ${this.curDeviceId}`), ]; if (grantPermissions) { tasks.push(this.getApkInfo(apks)); } const [, apkInfo] = await bluebird_1.default.all(tasks); if (grantPermissions && apkInfo) { // TODO: Simplify it after https://github.com/google/bundletool/issues/246 is implemented await this.grantAllPermissions(apkInfo.name); } } /** * Extracts and returns the full path to the master .apk file inside the bundle. * * @param apks - The full path to the .apks file * @returns The full path to the master bundle .apk * @throws {Error} If there was an error while extracting/finding the file */ async function extractBaseApk(apks) { return await extractFromApks(apks, ['splits', BASE_APK]); } /** * Extracts and returns the full path to the .apk, which contains the corresponding * resources for the given language in the .apks bundle. * * @param apks - The full path to the .apks file * @param language - The language abbreviation. The default language is * going to be selected if it is not set. * @returns The full path to the corresponding language .apk or the master .apk * if language split is not enabled for the bundle. * @throws {Error} If there was an error while extracting/finding the file */ async function extractLanguageApk(apks, language = null) { if (language) { try { return await extractFromApks(apks, ['splits', LANGUAGE_APK(language)]); } catch (e) { logger_1.log.debug(e.message); logger_1.log.info(`Assuming that splitting by language is not enabled for the '${apks}' bundle ` + `and returning the main apk instead`); return await this.extractBaseApk(apks); } } const defaultLanguages = ['en', 'en_us']; for (const lang of defaultLanguages) { try { return await extractFromApks(apks, ['splits', LANGUAGE_APK(lang)]); } catch { } } logger_1.log.info(`Cannot find any split apk for the default languages ${JSON.stringify(defaultLanguages)}. ` + `Returning the main apk instead.`); return await this.extractBaseApk(apks); } /** * * @param output * @returns */ function isTestPackageOnlyError(output) { return /\[INSTALL_FAILED_TEST_ONLY\]/.test(output); } //# sourceMappingURL=apks-utils.js.map