UNPKG

appium-adb

Version:

Android Debug Bridge interface

269 lines (245 loc) 9.63 kB
import { buildStartCmd, getPossibleActivityNames } from '../helpers.js'; import { exec } from 'teen_process'; import log from '../logger.js'; import path from 'path'; import _ from 'lodash'; import { sleep } from 'asyncbox'; import { fs } from 'appium-support'; let apkUtilsMethods = {}; apkUtilsMethods.isAppInstalled = async function (pkg) { try { let installed = false; log.debug(`Getting install status for ${pkg}`); let apiLevel = await this.getApiLevel(); let thirdparty = apiLevel >= 15 ? "-3" : ""; let stdout = await this.shell(['pm', 'list', 'packages', thirdparty, pkg]); let apkInstalledRgx = new RegExp(`^package:${pkg.replace(/(\.)/g, "\\$1")}$`, 'm'); installed = apkInstalledRgx.test(stdout); log.debug(`App is ${!installed ? " not" : ""} installed`); return installed; } catch (e) { log.errorAndThrow(`Error finding if app is installed. Original error: ${e.message}`); } }; apkUtilsMethods.startUri = async function (uri, pkg) { if (!uri || !pkg) { log.errorAndThrow("URI and package arguments are required"); } try { let args = ["am", "start", "-W", "-a", "android.intent.action.VIEW", "-d", uri, pkg]; await this.shell(args); } catch (e) { log.errorAndThrow(`Error attempting to start URI. Original error: ${e}`); } }; apkUtilsMethods.startApp = async function (startAppOptions = {}) { try { if (!startAppOptions.activity || !startAppOptions.pkg) { log.errorAndThrow("activity and pkg is required for launching application"); } startAppOptions = _.clone(startAppOptions); // initializing defaults _.defaults(startAppOptions, { waitPkg: startAppOptions.pkg, waitActivity: false, retry: true, stopApp: true }); // preventing null waitpkg startAppOptions.waitPkg = startAppOptions.waitPkg || startAppOptions.pkg; let apiLevel = await this.getApiLevel(); let cmd = buildStartCmd(startAppOptions, apiLevel); let stdout = await this.shell(cmd); if (stdout.indexOf("Error: Activity class") !== -1 && stdout.indexOf("does not exist") !== -1) { if (startAppOptions.retry && startAppOptions.activity[0] !== ".") { log.debug("We tried to start an activity that doesn't exist, " + "retrying with . prepended to activity"); startAppOptions.activity = `.${startAppOptions.activity}`; startAppOptions.retry = false; return this.startApp(startAppOptions); } else { log.errorAndThrow("Activity used to start app doesn't exist or cannot be " + "launched! Make sure it exists and is a launchable activity"); } } else if (stdout.indexOf("java.lang.SecurityException") !== -1) { // if the app is disabled on a real device it will throw a security exception log.errorAndThrow("Permission to start activity denied."); } if (startAppOptions.waitActivity) { await this.waitForActivity(startAppOptions.waitPkg, startAppOptions.waitActivity, startAppOptions.waitDuration); } } catch (e) { log.errorAndThrow(`Error occured while starting App. Original error: ${e.message}`); } }; apkUtilsMethods.getFocusedPackageAndActivity = async function () { log.debug("Getting focused package and activity"); let cmd = ['dumpsys', 'window', 'windows']; let nullRe = new RegExp(/mFocusedApp=null/); let searchRe = new RegExp('mFocusedApp.+Record\\{.*\\s([^\\s\\/\\}]+)' + '\\/([^\\s\\/\\}\\,]+)\\,?(\\s[^\\s\\/\\}]+)*\\}'); // https://regex101.com/r/xZ8vF7/1 try { let stdout = await this.shell(cmd); let foundNullMatch = false; for (let line of stdout.split("\n")) { let foundMatch = searchRe.exec(line); if (foundMatch) { return {appPackage: foundMatch[1].trim(), appActivity: foundMatch[2].trim()}; } else if (nullRe.test(line)) { foundNullMatch = true; } } if (foundNullMatch) { return {appPackage: null, appActivity: null}; } else { log.errorAndThrow("Could not parse activity from dumpsys"); } } catch (e) { log.errorAndThrow(`Could not get focusPackageAndActivity. Original error: ${e.message}`); } }; apkUtilsMethods.waitForActivityOrNot = async function (pkg, activity, not, waitMs = 20000) { if (!pkg || !activity) { throw new Error("Package and activity required."); } log.debug(`Waiting for pkg: '${pkg}' and activity: '${activity}'` + `${not ? ' not' : ''} to be focused`); let endAt = Date.now() + waitMs; let possibleActivityNames = []; let allActivities = activity.split(","); for (let oneActivity of allActivities) { oneActivity = oneActivity.trim(); possibleActivityNames.push(...getPossibleActivityNames(pkg, oneActivity)); } log.debug(`Possible activities, to be checked: ${possibleActivityNames.join(', ')}`); while (Date.now() < endAt) { let {appPackage, appActivity} = await this.getFocusedPackageAndActivity(); log.debug(`Found package: '${appPackage}' and activity: '${appActivity}'`); let foundAct = ((appPackage === pkg) && (_.findIndex(possibleActivityNames, possibleActivity => possibleActivity === appActivity) !== -1)); if ((!not && foundAct) || (not && !foundAct)) { return; } log.debug('Incorrect package and activity. Retrying.'); // cool down so we're not overloading device with requests await sleep(750); } let activityMessage = possibleActivityNames.join(" or "); log.errorAndThrow(`${pkg}/${activityMessage} never ${not ? 'stopped' : 'started'}`); }; apkUtilsMethods.waitForActivity = async function (pkg, act, waitMs = 20000) { await this.waitForActivityOrNot(pkg, act, false, waitMs); }; apkUtilsMethods.waitForNotActivity = async function (pkg, act, waitMs = 20000) { await this.waitForActivityOrNot(pkg, act, true, waitMs); }; apkUtilsMethods.uninstallApk = async function (pkg) { log.debug(`Uninstalling ${pkg}`); try { await this.forceStop(pkg); let stdout = await this.adbExec(['uninstall', pkg], {timeout: 20000}); stdout = stdout.trim(); // stdout may contain warnings meaning success is not on the first line. if (stdout.indexOf("Success") !== -1) { log.info("App was uninstalled"); return true; } else { log.info("App was not uninstalled, maybe it wasn't on device?"); return false; } } catch (e) { log.errorAndThrow(`Unable to uninstall APK. Original error: ${e.message}`); } }; apkUtilsMethods.installFromDevicePath = async function (apkPathOnDevice, opts = {}) { let stdout = await this.shell(['pm', 'install', '-r', apkPathOnDevice], opts); if (stdout.indexOf("Failure") !== -1) { log.errorAndThrow(`Remote install failed: ${stdout}`); } }; apkUtilsMethods.install = async function (apk, replace = true, timeout = 60000) { if (replace) { await this.adbExec(['install', '-r', apk], {timeout}); } else { await this.adbExec(['install', apk], {timeout}); } }; apkUtilsMethods.extractStringsFromApk = async function (apk, language, out) { log.debug(`Extracting strings for language: ${language || "default"}`); let stringsJson = 'strings.json'; let localPath; if (!language) { language = await this.getDeviceLanguage(); } let apkTools = this.jars['appium_apk_tools.jar']; let args = ['-jar', apkTools, 'stringsFromApk', apk, out, language]; let fileData, apkStrings; try { await exec('java', args); } catch (e) { log.debug(`No strings.xml for language '${language}', getting default ` + `strings.xml`); args.pop(); await exec('java', args); } try { log.debug("Reading strings from converted strings.json"); localPath = path.join(out, stringsJson); fileData = await fs.readFile(localPath, 'utf8'); apkStrings = JSON.parse(fileData); } catch (e) { if (fileData) { log.debug(`Content started with: ${fileData.slice(0, 300)}`); } let msg = `Could not parse strings from strings.json. Original ` + `error: ${e.message}`; log.errorAndThrow(msg); } return {apkStrings, localPath}; }; apkUtilsMethods.getDeviceLanguage = async function () { let language; if (await this.getApiLevel() < 23) { language = await this.getDeviceSysLanguage(); if (!language) { language = await this.getDeviceProductLanguage(); } } else { language = (await this.getDeviceLocale()).split("-")[0]; } return language; }; apkUtilsMethods.setDeviceLanguage = async function (language) { // this method is only used in API < 23 await this.setDeviceSysLanguage(language); }; apkUtilsMethods.getDeviceCountry = async function () { // this method is only used in API < 23 let country = await this.getDeviceSysCountry(); if (!country) { country = await this.getDeviceProductCountry(); } return country; }; apkUtilsMethods.setDeviceCountry = async function (country) { // this method is only used in API < 23 await this.setDeviceSysCountry(country); }; apkUtilsMethods.getDeviceLocale = async function () { // this method is only used in API >= 23 let locale = await this.getDeviceSysLocale(); if (!locale) { locale = await this.getDeviceProductLocale(); } return locale; }; apkUtilsMethods.setDeviceLocale = async function (locale) { // this method is only used in API >= 23 await this.setDeviceSysLocale(locale); }; export default apkUtilsMethods;