From 9613100d8c038e77041ef36d5b3fc88d2c70c0f6 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 5 Jan 2025 20:43:58 +0100 Subject: [PATCH] feat: Bump Eslint (#781) --- .eslintignore | 2 - .eslintrc.json | 11 - eslint.config.mjs | 10 + index.ts | 21 +- lib/adb.ts | 286 +++++++++++- lib/helpers.js | 93 ++-- lib/logcat.js | 4 +- lib/mixins.ts | 36 -- lib/options.ts | 2 +- lib/tools/aab-utils.js | 14 +- lib/tools/adb-commands.js | 438 +++++++++--------- lib/tools/adb-emu-commands.js | 127 +++-- lib/tools/android-manifest.js | 34 +- lib/tools/apk-signing.js | 41 +- lib/tools/apk-utils.js | 111 ++--- lib/tools/apks-utils.js | 39 +- lib/tools/index.ts | 43 -- lib/tools/keyboard-commands.js | 14 +- lib/tools/lockmgmt.js | 42 +- lib/tools/system-calls.js | 199 ++++---- package.json | 4 +- test/functional/adb-commands-e2e-specs.js | 1 - test/functional/adb-e2e-specs.js | 1 - test/functional/adb-emu-commands-e2e-specs.js | 1 - test/functional/android-manifest-e2e-specs.js | 7 +- test/functional/apk-signing-e2e-specs.js | 1 - test/functional/apk-utils-e2e-specs.js | 1 - test/functional/helpers-specs-e2e-specs.js | 1 - test/functional/lock-mgmt-e2e-specs.js | 4 +- test/functional/logcat-e2e-specs.js | 3 +- test/functional/syscalls-e2e-specs.js | 1 - test/unit/adb-commands-specs.js | 3 +- test/unit/adb-emu-commands-specs.js | 1 - test/unit/adb-specs.js | 1 - test/unit/apk-signing-specs.js | 1 - test/unit/apk-utils-specs.js | 7 +- test/unit/logcat-specs.js | 2 +- test/unit/syscalls-specs.js | 3 +- 38 files changed, 842 insertions(+), 768 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.json create mode 100644 eslint.config.mjs delete mode 100644 lib/mixins.ts delete mode 100644 lib/tools/index.ts diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index fb7020db..00000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -coverage -build diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 69a602c5..00000000 --- a/.eslintrc.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "@appium/eslint-config-appium", - "overrides": [ - { - "files": "test/**/*.js", - "rules": { - "func-names": "off" - } - } - ] -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..087ec2a2 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,10 @@ +import appiumConfig from '@appium/eslint-config-appium-ts'; + +export default [ + ...appiumConfig, + { + ignores: [ + 'keys/**', + ], + }, +]; diff --git a/index.ts b/index.ts index a500448b..e2158d0c 100644 --- a/index.ts +++ b/index.ts @@ -7,11 +7,24 @@ import {install} from 'source-map-support'; install(); -import {ADB} from './lib/adb'; - export * from './lib/adb'; -export type * from './lib/mixins'; -export type * from './lib/tools'; +// eslint-disable-next-line import/export +export {getAndroidBinaryPath} from './lib/tools/system-calls'; +export {getSdkRootFromEnv} from './lib/helpers'; +// TODO: move public typedefs into a separate file export type * from './lib/logcat'; export type * from './lib/options'; +export type * from './lib/tools/adb-commands'; +// eslint-disable-next-line import/export +export type * from './lib/tools/system-calls'; +export type * from './lib/tools/adb-emu-commands'; +export type * from './lib/tools/apk-signing'; +export type * from './lib/tools/apk-utils'; +export type * from './lib/tools/apks-utils'; +export type * from './lib/tools/aab-utils'; +export type * from './lib/tools/android-manifest'; +export type * from './lib/tools/keyboard-commands'; +export type * from './lib/tools/lockmgmt'; + +import {ADB} from './lib/adb'; export default ADB; diff --git a/lib/adb.ts b/lib/adb.ts index 262183b9..2306b377 100644 --- a/lib/adb.ts +++ b/lib/adb.ts @@ -1,14 +1,30 @@ import _ from 'lodash'; import os from 'node:os'; -import methods, {getAndroidBinaryPath} from './tools'; -import {DEFAULT_ADB_EXEC_TIMEOUT, requireSdkRoot, getSdkRootFromEnv} from './helpers'; +import { + DEFAULT_ADB_EXEC_TIMEOUT, + requireSdkRoot, + getSdkRootFromEnv +} from './helpers'; import log from './logger'; import type {ADBOptions, ADBExecutable} from './options'; -import type { LogcatOpts } from './logcat'; +import type { LogcatOpts, Logcat } from './logcat'; import type { LRUCache } from 'lru-cache'; import type { ExecError } from 'teen_process'; +import type { StringRecord } from '@appium/types'; -const DEFAULT_ADB_PORT = 5037; +import * as generalMethods from './tools/adb-commands'; +import * as manifestMethods from './tools/android-manifest'; +import * as systemCallMethods from './tools/system-calls'; +import * as apkSigningMethods from './tools/apk-signing'; +import * as apkUtilsMethods from './tools/apk-utils'; +import * as apksUtilsMethods from './tools/apks-utils'; +import * as aabUtilsMethods from './tools/aab-utils'; +import * as emuMethods from './tools/adb-emu-commands'; +import * as lockManagementCommands from './tools/lockmgmt'; +import * as keyboardCommands from './tools/keyboard-commands'; + + +export const DEFAULT_ADB_PORT = 5037; export const DEFAULT_OPTS = { sdkRoot: getSdkRootFromEnv(), executable: {path: 'adb', defaultArgs: []}, @@ -21,7 +37,7 @@ export const DEFAULT_OPTS = { allowDelayAdb: true, } as const; -export class ADB { +export class ADB implements ADBOptions { adbHost?: string; adbPort?: number; _apiLevel: number|undefined; @@ -35,7 +51,30 @@ export class ADB { remoteAppsCache: LRUCache|undefined; _isLockManagementSupported: boolean|undefined; + sdkRoot?: string; + udid?: string; + appDeviceReadyTimeout?: number; + useKeystore?: boolean; + keystorePath?: string; + keystorePassword?: string; + keyAlias?: string; + keyPassword?: string; executable: ADBExecutable; + tmpDir?: string; + curDeviceId?: string; + emulatorPort?: number; + logcat?: Logcat; + binaries?: StringRecord; + suppressKillServer?: boolean; + adbExecTimeout?: number; + remoteAppsCacheLimit?: number; + buildToolsVersion?: string; + allowOfflineDevices?: boolean; + allowDelayAdb?: boolean; + remoteAdbHost?: string; + remoteAdbPort?: number; + clearDeviceLogsOnStart?: boolean; + constructor(opts: ADBOptions = ({} as ADBOptions)) { const options: ADBOptions = _.defaultsDeep(opts, _.cloneDeep(DEFAULT_OPTS)); _.defaultsDeep(this, options); @@ -95,9 +134,238 @@ export class ADB { } return adb; } -} -// add all the methods to the ADB prototype -Object.assign(ADB.prototype, methods); + // TODO: Group methods from general to corresponding modules + shellChunks = generalMethods.shellChunks; + getAdbWithCorrectAdbPath = generalMethods.getAdbWithCorrectAdbPath; + initAapt = generalMethods.initAapt; + initAapt2 = generalMethods.initAapt2; + initZipAlign = generalMethods.initZipAlign; + initBundletool = generalMethods.initBundletool; + getApiLevel = generalMethods.getApiLevel; + getPlatformVersion = generalMethods.getPlatformVersion; + isDeviceConnected = generalMethods.isDeviceConnected; + mkdir = generalMethods.mkdir; + isValidClass = generalMethods.isValidClass; + resolveLaunchableActivity = generalMethods.resolveLaunchableActivity; + forceStop = generalMethods.forceStop; + killPackage = generalMethods.killPackage; + clear = generalMethods.clear; + grantAllPermissions = generalMethods.grantAllPermissions; + grantPermissions = generalMethods.grantPermissions; + grantPermission = generalMethods.grantPermission; + revokePermission = generalMethods.revokePermission; + getGrantedPermissions = generalMethods.getGrantedPermissions; + getDeniedPermissions = generalMethods.getDeniedPermissions; + getReqPermissions = generalMethods.getReqPermissions; + getLocationProviders = generalMethods.getLocationProviders; + toggleGPSLocationProvider = generalMethods.toggleGPSLocationProvider; + setHiddenApiPolicy = generalMethods.setHiddenApiPolicy; + setDefaultHiddenApiPolicy = generalMethods.setDefaultHiddenApiPolicy; + stopAndClear = generalMethods.stopAndClear; + availableIMEs = generalMethods.availableIMEs; + enabledIMEs = generalMethods.enabledIMEs; + enableIME = generalMethods.enableIME; + disableIME = generalMethods.disableIME; + setIME = generalMethods.setIME; + defaultIME = generalMethods.defaultIME; + keyevent = generalMethods.keyevent; + inputText = generalMethods.inputText; + clearTextField = generalMethods.clearTextField; + lock = generalMethods.lock; + back = generalMethods.back; + goToHome = generalMethods.goToHome; + getAdbPath = generalMethods.getAdbPath; + getScreenOrientation = generalMethods.getScreenOrientation; + sendTelnetCommand = generalMethods.sendTelnetCommand; + isAirplaneModeOn = generalMethods.isAirplaneModeOn; + setAirplaneMode = generalMethods.setAirplaneMode; + setBluetoothOn = generalMethods.setBluetoothOn; + setNfcOn = generalMethods.setNfcOn; + broadcastAirplaneMode = generalMethods.broadcastAirplaneMode; + isWifiOn = generalMethods.isWifiOn; + isDataOn = generalMethods.isDataOn; + isAnimationOn = generalMethods.isAnimationOn; + setAnimationScale = generalMethods.setAnimationScale; + rimraf = generalMethods.rimraf; + push = generalMethods.push; + pull = generalMethods.pull; + processExists = generalMethods.processExists; + getForwardList = generalMethods.getForwardList; + forwardPort = generalMethods.forwardPort; + removePortForward = generalMethods.removePortForward; + getReverseList = generalMethods.getReverseList; + reversePort = generalMethods.reversePort; + removePortReverse = generalMethods.removePortReverse; + forwardAbstractPort = generalMethods.forwardAbstractPort; + ping = generalMethods.ping; + restart = generalMethods.restart; + startLogcat = generalMethods.startLogcat; + stopLogcat = generalMethods.stopLogcat; + getLogcatLogs = generalMethods.getLogcatLogs; + setLogcatListener = generalMethods.setLogcatListener; + removeLogcatListener = generalMethods.removeLogcatListener; + listProcessStatus = generalMethods.listProcessStatus; + getNameByPid = generalMethods.getNameByPid; + getPIDsByName = generalMethods.getPIDsByName; + killProcessesByName = generalMethods.killProcessesByName; + killProcessByPID = generalMethods.killProcessByPID; + broadcastProcessEnd = generalMethods.broadcastProcessEnd; + broadcast = generalMethods.broadcast; + getDeviceProperty = generalMethods.getDeviceProperty; + setDeviceProperty = generalMethods.setDeviceProperty; + getDeviceSysLanguage = generalMethods.getDeviceSysLanguage; + getDeviceSysCountry = generalMethods.getDeviceSysCountry; + getDeviceSysLocale = generalMethods.getDeviceSysLocale; + getDeviceProductLanguage = generalMethods.getDeviceProductLanguage; + getDeviceProductCountry = generalMethods.getDeviceProductCountry; + getDeviceProductLocale = generalMethods.getDeviceProductLocale; + getModel = generalMethods.getModel; + getManufacturer = generalMethods.getManufacturer; + getScreenSize = generalMethods.getScreenSize; + getScreenDensity = generalMethods.getScreenDensity; + setHttpProxy = generalMethods.setHttpProxy; + deleteHttpProxy = generalMethods.deleteHttpProxy; + setSetting = generalMethods.setSetting; + getSetting = generalMethods.getSetting; + bugreport = generalMethods.bugreport; + screenrecord = generalMethods.screenrecord; + runInImeContext = generalMethods.runInImeContext; + getTimeZone = generalMethods.getTimeZone; + listFeatures = generalMethods.listFeatures; + isStreamedInstallSupported = generalMethods.isStreamedInstallSupported; + isIncrementalInstallSupported = generalMethods.isIncrementalInstallSupported; + getDeviceIdleWhitelist = generalMethods.getDeviceIdleWhitelist; + addToDeviceIdleWhitelist = generalMethods.addToDeviceIdleWhitelist; + takeScreenshot = generalMethods.takeScreenshot; + setWifiState = generalMethods.setWifiState; + setDataState = generalMethods.setDataState; + listPorts = generalMethods.listPorts; + + executeApksigner = apkSigningMethods.executeApksigner; + signWithDefaultCert = apkSigningMethods.signWithDefaultCert; + signWithCustomCert = apkSigningMethods.signWithCustomCert; + sign = apkSigningMethods.sign; + zipAlignApk = apkSigningMethods.zipAlignApk; + checkApkCert = apkSigningMethods.checkApkCert; + getKeystoreHash = apkSigningMethods.getKeystoreHash; + + APP_INSTALL_STATE = apkUtilsMethods.APP_INSTALL_STATE; + isAppInstalled = apkUtilsMethods.isAppInstalled; + startUri = apkUtilsMethods.startUri; + startApp = apkUtilsMethods.startApp; + dumpWindows = apkUtilsMethods.dumpWindows; + getFocusedPackageAndActivity = apkUtilsMethods.getFocusedPackageAndActivity; + waitForActivityOrNot = apkUtilsMethods.waitForActivityOrNot; + waitForActivity = apkUtilsMethods.waitForActivity; + waitForNotActivity = apkUtilsMethods.waitForNotActivity; + uninstallApk = apkUtilsMethods.uninstallApk; + installFromDevicePath = apkUtilsMethods.installFromDevicePath; + cacheApk = apkUtilsMethods.cacheApk; + install = apkUtilsMethods.install; + getApplicationInstallState = apkUtilsMethods.getApplicationInstallState; + installOrUpgrade = apkUtilsMethods.installOrUpgrade; + extractStringsFromApk = apkUtilsMethods.extractStringsFromApk; + getDeviceLanguage = apkUtilsMethods.getDeviceLanguage; + getDeviceCountry = apkUtilsMethods.getDeviceCountry; + getDeviceLocale = apkUtilsMethods.getDeviceLocale; + ensureCurrentLocale = apkUtilsMethods.ensureCurrentLocale; + getApkInfo = apkUtilsMethods.getApkInfo; + getPackageInfo = apkUtilsMethods.getPackageInfo; + pullApk = apkUtilsMethods.pullApk; + activateApp = apkUtilsMethods.activateApp; + + hideKeyboard = keyboardCommands.hideKeyboard; + isSoftKeyboardPresent = keyboardCommands.isSoftKeyboardPresent; -export {DEFAULT_ADB_PORT, getAndroidBinaryPath, getSdkRootFromEnv}; + isLockManagementSupported = lockManagementCommands.isLockManagementSupported; + verifyLockCredential = lockManagementCommands.verifyLockCredential; + clearLockCredential = lockManagementCommands.clearLockCredential; + isLockEnabled = lockManagementCommands.isLockEnabled; + setLockCredential = lockManagementCommands.setLockCredential; + isScreenLocked = lockManagementCommands.isScreenLocked; + dismissKeyguard = lockManagementCommands.dismissKeyguard; + cycleWakeUp = lockManagementCommands.cycleWakeUp; + + getSdkBinaryPath = systemCallMethods.getSdkBinaryPath; + getBinaryNameForOS = systemCallMethods.getBinaryNameForOS; + getBinaryFromSdkRoot = systemCallMethods.getBinaryFromSdkRoot; + getBinaryFromPath = systemCallMethods.getBinaryFromPath; + getConnectedDevices = systemCallMethods.getConnectedDevices; + getDevicesWithRetry = systemCallMethods.getDevicesWithRetry; + reconnect = systemCallMethods.reconnect; + restartAdb = systemCallMethods.restartAdb; + killServer = systemCallMethods.killServer; + resetTelnetAuthToken = systemCallMethods.resetTelnetAuthToken; + adbExecEmu = systemCallMethods.adbExecEmu; + EXEC_OUTPUT_FORMAT = systemCallMethods.EXEC_OUTPUT_FORMAT; + adbExec = systemCallMethods.adbExec; + shell = systemCallMethods.shell; + createSubProcess = systemCallMethods.createSubProcess; + getAdbServerPort = systemCallMethods.getAdbServerPort; + getEmulatorPort = systemCallMethods.getEmulatorPort; + getPortFromEmulatorString = systemCallMethods.getPortFromEmulatorString; + getConnectedEmulators = systemCallMethods.getConnectedEmulators; + setEmulatorPort = systemCallMethods.setEmulatorPort; + setDeviceId = systemCallMethods.setDeviceId; + setDevice = systemCallMethods.setDevice; + getRunningAVD = systemCallMethods.getRunningAVD; + getRunningAVDWithRetry = systemCallMethods.getRunningAVDWithRetry; + killAllEmulators = systemCallMethods.killAllEmulators; + killEmulator = systemCallMethods.killEmulator; + launchAVD = systemCallMethods.launchAVD; + getVersion = systemCallMethods.getVersion; + waitForEmulatorReady = systemCallMethods.waitForEmulatorReady; + waitForDevice = systemCallMethods.waitForDevice; + reboot = systemCallMethods.reboot; + changeUserPrivileges = systemCallMethods.changeUserPrivileges; + root = systemCallMethods.root; + unroot = systemCallMethods.unroot; + isRoot = systemCallMethods.isRoot; + fileExists = systemCallMethods.fileExists; + ls = systemCallMethods.ls; + fileSize = systemCallMethods.fileSize; + installMitmCertificate = systemCallMethods.installMitmCertificate; + isMitmCertificateInstalled = systemCallMethods.isMitmCertificateInstalled; + + execBundletool = apksUtilsMethods.execBundletool; + getDeviceSpec = apksUtilsMethods.getDeviceSpec; + installMultipleApks = apksUtilsMethods.installMultipleApks; + installApks = apksUtilsMethods.installApks; + extractBaseApk = apksUtilsMethods.extractBaseApk; + extractLanguageApk = apksUtilsMethods.extractLanguageApk; + isTestPackageOnlyError = apksUtilsMethods.isTestPackageOnlyError; + + packageAndLaunchActivityFromManifest = manifestMethods.packageAndLaunchActivityFromManifest; + targetSdkVersionFromManifest = manifestMethods.targetSdkVersionFromManifest; + targetSdkVersionUsingPKG = manifestMethods.targetSdkVersionUsingPKG; + compileManifest = manifestMethods.compileManifest; + insertManifest = manifestMethods.insertManifest; + hasInternetPermissionFromManifest = manifestMethods.hasInternetPermissionFromManifest; + + extractUniversalApk = aabUtilsMethods.extractUniversalApk; + + isEmulatorConnected = emuMethods.isEmulatorConnected; + verifyEmulatorConnected = emuMethods.verifyEmulatorConnected; + fingerprint = emuMethods.fingerprint; + rotate = emuMethods.rotate; + powerAC = emuMethods.powerAC; + sensorSet = emuMethods.sensorSet; + powerCapacity = emuMethods.powerCapacity; + powerOFF = emuMethods.powerOFF; + sendSMS = emuMethods.sendSMS; + gsmCall = emuMethods.gsmCall; + gsmSignal = emuMethods.gsmSignal; + gsmVoice = emuMethods.gsmVoice; + networkSpeed = emuMethods.networkSpeed; + execEmuConsoleCommand = emuMethods.execEmuConsoleCommand; + getEmuVersionInfo = emuMethods.getEmuVersionInfo; + getEmuImageProperties = emuMethods.getEmuImageProperties; + checkAvdExist = emuMethods.checkAvdExist; + POWER_AC_STATES = emuMethods.POWER_AC_STATES; + GSM_CALL_ACTIONS = emuMethods.GSM_CALL_ACTIONS; + GSM_VOICE_STATES = emuMethods.GSM_VOICE_STATES; + GSM_SIGNAL_STRENGTHS = emuMethods.GSM_SIGNAL_STRENGTHS; + NETWORK_SPEED = emuMethods.NETWORK_SPEED; + SENSORS = emuMethods.SENSORS; +} diff --git a/lib/helpers.js b/lib/helpers.js index 35724fcb..ef67c324 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -3,15 +3,15 @@ import { system, fs, zip, util, tempDir } from '@appium/support'; import log from './logger.js'; import _ from 'lodash'; import B from 'bluebird'; -import semver from 'semver'; +import * as semver from 'semver'; import os from 'os'; import { exec } from 'teen_process'; -const APKS_EXTENSION = '.apks'; -const APK_EXTENSION = '.apk'; -const APK_INSTALL_TIMEOUT = 60000; -const APKS_INSTALL_TIMEOUT = APK_INSTALL_TIMEOUT * 2; -const DEFAULT_ADB_EXEC_TIMEOUT = 20000; // in milliseconds +export const APKS_EXTENSION = '.apks'; +export const APK_EXTENSION = '.apk'; +export const APK_INSTALL_TIMEOUT = 60000; +export const APKS_INSTALL_TIMEOUT = APK_INSTALL_TIMEOUT * 2; +export const DEFAULT_ADB_EXEC_TIMEOUT = 20000; // in milliseconds const MAIN_ACTION = 'android.intent.action.MAIN'; const LAUNCHER_CATEGORY = 'android.intent.category.LAUNCHER'; const MODULE_NAME = 'appium-adb'; @@ -32,7 +32,7 @@ const getModuleRoot = _.memoize(async function getModuleRoot () { JSON.parse(await fs.readFile(manifestPath, 'utf8')).name === MODULE_NAME) { return moduleRoot; } - } catch (ign) {} + } catch {} moduleRoot = path.dirname(moduleRoot); isAtFsRoot = moduleRoot.length <= path.dirname(moduleRoot).length; } @@ -49,7 +49,7 @@ const getModuleRoot = _.memoize(async function getModuleRoot () { * @returns {Promise} The full path to the resource * @throws {Error} If the absolute resource path cannot be determined */ -const getResourcePath = _.memoize(async function getResourcePath (relPath) { +export const getResourcePath = _.memoize(async function getResourcePath (relPath) { const moduleRoot = await getModuleRoot(); const resultPath = path.resolve(moduleRoot, relPath); if (!await fs.exists(resultPath)) { @@ -64,7 +64,7 @@ const getResourcePath = _.memoize(async function getResourcePath (relPath) { * * @return {string|undefined} The full path to the SDK root folder */ -function getSdkRootFromEnv () { +export function getSdkRootFromEnv () { return process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT; } @@ -76,7 +76,7 @@ function getSdkRootFromEnv () { * @throws {Error} If either the corresponding env variable is unset or is * pointing to an invalid file system entry */ -async function requireSdkRoot (customRoot = null) { +export async function requireSdkRoot (customRoot = null) { const sdkRoot = customRoot || getSdkRootFromEnv(); const docMsg = 'Read https://developer.android.com/studio/command-line/variables for more details'; if (_.isEmpty(sdkRoot)) { @@ -107,7 +107,7 @@ async function requireSdkRoot (customRoot = null) { * @param {string} sdkRoot * @return {Promise} The resulting path to the newest installed platform. */ -async function getAndroidPlatformAndPath (sdkRoot) { +export async function getAndroidPlatformAndPath (sdkRoot) { const propsPaths = await fs.glob('*/build.prop', { cwd: path.resolve(sdkRoot, 'platforms'), absolute: true, @@ -147,7 +147,7 @@ async function getAndroidPlatformAndPath (sdkRoot) { * @param {string} zipPath * @param {string} dstRoot */ -async function unzipFile (zipPath, dstRoot = path.dirname(zipPath)) { +export async function unzipFile (zipPath, dstRoot = path.dirname(zipPath)) { log.debug(`Unzipping '${zipPath}' to '${dstRoot}'`); await zip.assertValidZip(zipPath); await zip.extractAllTo(zipPath, dstRoot); @@ -164,7 +164,7 @@ async function unzipFile (zipPath, dstRoot = path.dirname(zipPath)) { * unsigned and overwritten * @throws {Error} if there was an error during the unsign operation */ -async function unsignApk (apkPath) { +export async function unsignApk (apkPath) { const tmpRoot = await tempDir.openDir(); const metaInfFolderName = 'META-INF'; try { @@ -196,7 +196,7 @@ async function unsignApk (apkPath) { * @param {string} stdout * @returns {string[]} */ -function getIMEListFromOutput (stdout) { +export function getIMEListFromOutput (stdout) { let engines = []; for (let line of stdout.split('\n')) { if (line.length > 0 && line[0] !== ' ') { @@ -208,7 +208,7 @@ function getIMEListFromOutput (stdout) { } /** @type {() => Promise} */ -const getJavaHome = _.memoize(async function getJavaHome () { +export const getJavaHome = _.memoize(async function getJavaHome () { const result = process.env.JAVA_HOME; if (!result) { throw new Error('The JAVA_HOME environment variable is not set for the current process'); @@ -224,7 +224,7 @@ const getJavaHome = _.memoize(async function getJavaHome () { }); /** @type {() => Promise} */ -const getJavaForOs = _.memoize(async function getJavaForOs () { +export const getJavaForOs = _.memoize(async function getJavaForOs () { let javaHome; let errMsg; try { @@ -241,17 +241,17 @@ const getJavaForOs = _.memoize(async function getJavaForOs () { } try { return await fs.which(executableName); - } catch (ign) {} + } catch {} throw new Error(`The '${executableName}' binary could not be found ` + `neither in PATH nor under JAVA_HOME (${javaHome ? path.resolve(javaHome, 'bin') : errMsg})`); }); /** @type {() => Promise} */ -const getOpenSslForOs = async function () { +export const getOpenSslForOs = async function () { const binaryName = `openssl${system.isWindows() ? '.exe' : ''}`; try { return await fs.which(binaryName); - } catch (err) { + } catch { throw new Error('The openssl tool must be installed on the system and available on the path'); } }; @@ -263,7 +263,7 @@ const getOpenSslForOs = async function () { * @returns {Promise} An absolute path to apksigner tool. * @throws {Error} If the tool is not present on the local file system. */ -async function getApksignerForOs (sysHelpers) { +export async function getApksignerForOs (sysHelpers) { return await sysHelpers.getBinaryFromSdkRoot('apksigner.jar'); } @@ -275,7 +275,7 @@ async function getApksignerForOs (sysHelpers) { * @returns {Promise} An absolute path to apkanalyzer tool. * @throws {Error} If the tool is not present on the local file system. */ -async function getApkanalyzerForOs (sysHelpers) { +export async function getApkanalyzerForOs (sysHelpers) { return await sysHelpers.getBinaryFromSdkRoot('apkanalyzer'); } @@ -295,7 +295,7 @@ async function getApkanalyzerForOs (sysHelpers) { * @param {string} dumpsys - The output of dumpsys window command. * @return {boolean} True if lock screen is showing. */ -function isShowingLockscreen (dumpsys) { +export function isShowingLockscreen (dumpsys) { return _.some(['mShowingLockscreen=true', 'mDreamingLockscreen=true'], (x) => dumpsys.includes(x)) // `mIsShowing` and `mInputRestricted` are `true` in lock condition. `false` is unlock condition. || _.every([/KeyguardStateMonitor[\n\s]+mIsShowing=true/, /\s+mInputRestricted=true/], (x) => x.test(dumpsys)); @@ -316,7 +316,7 @@ export function isInDozingMode(dumpsys) { /* * Checks mCurrentFocus in dumpsys output to determine if Keyguard is activated */ -function isCurrentFocusOnKeyguard (dumpsys) { +export function isCurrentFocusOnKeyguard (dumpsys) { let m = /mCurrentFocus.+Keyguard/gi.exec(dumpsys); return (m && m.length && m[0]) ? true : false; } @@ -324,7 +324,7 @@ function isCurrentFocusOnKeyguard (dumpsys) { /* * Reads SurfaceOrientation in dumpsys output */ -function getSurfaceOrientation (dumpsys) { +export function getSurfaceOrientation (dumpsys) { let m = /SurfaceOrientation: \d/gi.exec(dumpsys); return m && parseInt(m[0].split(':')[1], 10); } @@ -333,7 +333,7 @@ function getSurfaceOrientation (dumpsys) { * Checks mScreenOnFully in dumpsys output to determine if screen is showing * Default is true */ -function isScreenOnFully (dumpsys) { +export function isScreenOnFully (dumpsys) { let m = /mScreenOnFully=\w+/gi.exec(dumpsys); return !m || // if information is missing we assume screen is fully on (m && m.length > 0 && m[0].split('=')[1] === 'true') || false; @@ -347,7 +347,7 @@ function isScreenOnFully (dumpsys) { * @param {number} apiLevel - The actual OS API level * @returns {string[]} The actual command line array */ -function buildStartCmd (startAppOptions, apiLevel) { +export function buildStartCmd (startAppOptions, apiLevel) { const { user, waitForLaunch, @@ -403,7 +403,7 @@ function buildStartCmd (startAppOptions, apiLevel) { // have internal hyphens let optionalIntentArguments = ` ${startAppOptions.optionalIntentArguments}`; let re = / (-[^\s]+) (.+)/; - while (true) { // eslint-disable-line no-constant-condition + while (true) { let args = re.exec(optionalIntentArguments); if (!args) { if (optionalIntentArguments.length) { @@ -435,7 +435,7 @@ function buildStartCmd (startAppOptions, apiLevel) { } /** @type {() => Promise<{major: number, minor: number, build: number}?>} */ -const getSdkToolsVersion = _.memoize(async function getSdkToolsVersion () { +export const getSdkToolsVersion = _.memoize(async function getSdkToolsVersion () { const androidHome = process.env.ANDROID_HOME; if (!androidHome) { throw new Error('ANDROID_HOME environment variable is expected to be set'); @@ -465,7 +465,7 @@ const getSdkToolsVersion = _.memoize(async function getSdkToolsVersion () { * * @type {(sdkRoot: string) => Promise} */ -const getBuildToolsDirs = _.memoize(async function getBuildToolsDirs (sdkRoot) { +export const getBuildToolsDirs = _.memoize(async function getBuildToolsDirs (sdkRoot) { let buildToolsDirs = await fs.glob('*/', { cwd: path.resolve(sdkRoot, 'build-tools'), absolute: true, @@ -502,7 +502,7 @@ const getBuildToolsDirs = _.memoize(async function getBuildToolsDirs (sdkRoot) { * No filtering is done if the parameter is not set. * @returns {string[]} The list of matched permission names or an empty list if no matches were found. */ -const extractMatchingPermissions = function (dumpsysOutput, groupNames, grantedState = null) { +export const extractMatchingPermissions = function (dumpsysOutput, groupNames, grantedState = null) { const groupPatternByName = (groupName) => new RegExp(`^(\\s*${_.escapeRegExp(groupName)} permissions:[\\s\\S]+)`, 'm'); const indentPattern = /\S|$/; const permissionNamePattern = /android\.\w*\.?permission\.\w+/; @@ -573,7 +573,7 @@ const extractMatchingPermissions = function (dumpsysOutput, groupNames, grantedS * @param {InstallOptions} [options={}] - The options mapping to transform * @returns {string[]} The array of arguments */ -function buildInstallArgs (apiLevel, options = {}) { +export function buildInstallArgs (apiLevel, options = {}) { const result = []; if (!util.hasValue(options.replace) || options.replace) { @@ -611,7 +611,7 @@ function buildInstallArgs (apiLevel, options = {}) { * values are represented as arrays. If no config found for the * given marker then an empty mapping is returned. */ -function parseAaptStrings (rawOutput, configMarker) { +export function parseAaptStrings (rawOutput, configMarker) { const normalizeStringMatch = function (s) { return s.replace(/"$/, '').replace(/^"/, '').replace(/\\"/g, '"'); }; @@ -691,7 +691,7 @@ function parseAaptStrings (rawOutput, configMarker) { * values are represented as arrays. If no config found for the * given marker then an empty mapping is returned. */ -function parseAapt2Strings (rawOutput, configMarker) { +export function parseAapt2Strings (rawOutput, configMarker) { const allLines = rawOutput.split(os.EOL); function extractContent (startIdx) { let idx = startIdx; @@ -807,7 +807,7 @@ function parseAapt2Strings (rawOutput, configMarker) { * @param {string} defaultMarker The default config marker value * @return {Promise} The formatted config marker */ -async function formatConfigMarker (configsGetter, desiredMarker, defaultMarker) { +export async function formatConfigMarker (configsGetter, desiredMarker, defaultMarker) { let configMarker = desiredMarker || defaultMarker; if (configMarker.includes('-') && !configMarker.includes('-r')) { configMarker = configMarker.replace('-', '-r'); @@ -835,7 +835,7 @@ async function formatConfigMarker (configsGetter, desiredMarker, defaultMarker) * @returns {Array} The generated arguments. The * resulting array might be empty if both arguments are empty */ -function toAvdLocaleArgs (language, country) { +export function toAvdLocaleArgs (language, country) { const result = []; if (language && _.isString(language)) { result.push('-prop', `persist.sys.language=${language.toLowerCase()}`); @@ -862,7 +862,7 @@ function toAvdLocaleArgs (language, country) { * * @returns {Promise} The full path to the folder or `null` if the folder cannot be found */ -async function getAndroidPrefsRoot () { +export async function getAndroidPrefsRoot () { let location = process.env.ANDROID_EMULATOR_HOME; if (await dirExists(location ?? '')) { return location ?? null; @@ -891,7 +891,7 @@ async function getAndroidPrefsRoot () { * @param {string} location The full path to the directory * @returns {Promise} */ -async function dirExists (location) { +export async function dirExists (location) { return await fs.exists(location) && (await fs.stat(location)).isDirectory(); } @@ -905,7 +905,7 @@ async function dirExists (location) { * @param {string} arg Non-escaped argument string * @returns The escaped argument */ -function escapeShellArg (arg) { +export function escapeShellArg (arg) { arg = `${arg}`; if (system.isWindows()) { return /[&|^\s]/.test(arg) ? `"${arg.replace(/"/g, '""')}"` : arg; @@ -926,7 +926,7 @@ function escapeShellArg (arg) { * with the expectation that the app manifest could be parsed next * in order to determine category names for these. */ -function parseLaunchableActivityNames (dumpsys) { +export function parseLaunchableActivityNames (dumpsys) { const mainActivityNameRe = new RegExp(`^\\s*${_.escapeRegExp(MAIN_ACTION)}:$`); const categoryNameRe = /^\s*Category:\s+"([a-zA-Z0-9._/-]+)"$/; const blocks = []; @@ -1003,7 +1003,7 @@ function parseLaunchableActivityNames (dumpsys) { * @return {RegExpExecArray?} The result of Regexp.exec operation * or _null_ if no matches are found */ -function matchComponentName (classString) { +export function matchComponentName (classString) { // some.package/some.package.Activity return /^[\p{L}0-9./_]+$/u.exec(classString); } @@ -1155,16 +1155,3 @@ export async function readPackageManifest(apkPath) { } return result; } - -export { - getAndroidPlatformAndPath, unzipFile, - getIMEListFromOutput, getJavaForOs, isShowingLockscreen, isCurrentFocusOnKeyguard, - getSurfaceOrientation, isScreenOnFully, buildStartCmd, getJavaHome, - getSdkToolsVersion, getApksignerForOs, getBuildToolsDirs, - getApkanalyzerForOs, getOpenSslForOs, extractMatchingPermissions, APKS_EXTENSION, - APK_INSTALL_TIMEOUT, APKS_INSTALL_TIMEOUT, buildInstallArgs, APK_EXTENSION, - DEFAULT_ADB_EXEC_TIMEOUT, parseAaptStrings, parseAapt2Strings, - formatConfigMarker, unsignApk, toAvdLocaleArgs, requireSdkRoot, - getSdkRootFromEnv, getAndroidPrefsRoot, dirExists, escapeShellArg, - parseLaunchableActivityNames, matchComponentName, getResourcePath -}; diff --git a/lib/logcat.js b/lib/logcat.js index 7f50b3cf..45a780ab 100644 --- a/lib/logcat.js +++ b/lib/logcat.js @@ -107,7 +107,7 @@ function formatFilterSpecs (filterSpecs) { } -class Logcat extends EventEmitter { +export class Logcat extends EventEmitter { constructor (opts = {}) { super(); this.adb = opts.adb; @@ -125,7 +125,7 @@ class Logcat extends EventEmitter { async startCapture (opts = {}) { let started = false; - return await new B(async (_resolve, _reject) => { // eslint-disable-line promise/param-names + return await new B(async (_resolve, _reject) => { const resolve = function (...args) { started = true; _resolve(...args); diff --git a/lib/mixins.ts b/lib/mixins.ts deleted file mode 100644 index bebc2cdf..00000000 --- a/lib/mixins.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @privateRemarks - * This is just an interface which mixes methods into the root `ADB` class. - * - * @module - */ - -import { - SystemCalls, - ApkUtils, - ADBCommands, - ADBEmuCommands, - LockManagementCommands, - ManifestMethods, - KeyboardCommands, - ApkSigningCommands, - ApksUtils, - AabUtils, -} from './tools'; -import {ADBOptions} from './options'; - -declare module './adb' { - // note that ADBOptions is the options object, but it's mixed directly in to the instance in the constructor. - interface ADB - extends ADBCommands, - AabUtils, - ApkUtils, - ApksUtils, - SystemCalls, - ADBOptions, - ADBEmuCommands, - LockManagementCommands, - ManifestMethods, - KeyboardCommands, - ApkSigningCommands {} -} diff --git a/lib/options.ts b/lib/options.ts index 7fca87db..acb0231c 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -1,4 +1,4 @@ -import type Logcat from './logcat'; +import type {Logcat} from './logcat'; import type {StringRecord} from '@appium/types'; export interface ADBOptions { diff --git a/lib/tools/aab-utils.js b/lib/tools/aab-utils.js index d03a2b6a..1a4849f8 100644 --- a/lib/tools/aab-utils.js +++ b/lib/tools/aab-utils.js @@ -15,8 +15,6 @@ const AAB_CACHE = new LRUCache({ const AAB_CACHE_GUARD = new AsyncLock(); const UNIVERSAL_APK = 'universal.apk'; -const aabUtilsMethods = {}; - process.on('exit', () => { if (!AAB_CACHE.size) { return; @@ -65,7 +63,7 @@ process.on('exit', () => { * by default. * @throws {Error} If there was an error while creating the universal .apk */ -aabUtilsMethods.extractUniversalApk = async function extractUniversalApk (aabPath, opts = {}) { +export async function extractUniversalApk (aabPath, opts = {}) { if (!await fs.exists(aabPath)) { throw new Error(`The file at '${aabPath}' either does not exist or is not accessible`); } @@ -139,7 +137,7 @@ aabUtilsMethods.extractUniversalApk = async function extractUniversalApk (aabPat } try { await B.all(fileDeletionPromises); - } catch (ign) {} + } catch {} if (!universalApkPath) { log.debug(`The following items were extracted from the .aab bundle: ${allFileNames}`); throw new Error(`${UNIVERSAL_APK} cannot be found in '${aabPath}' bundle. ` + @@ -155,10 +153,4 @@ aabUtilsMethods.extractUniversalApk = async function extractUniversalApk (aabPat await fs.rimraf(tmpRoot); throw e; } -}; - -export default aabUtilsMethods; - -/** - * @typedef {typeof aabUtilsMethods} AabUtils - */ +} diff --git a/lib/tools/adb-commands.js b/lib/tools/adb-commands.js index 06fc580d..8e4fb3ad 100644 --- a/lib/tools/adb-commands.js +++ b/lib/tools/adb-commands.js @@ -7,7 +7,7 @@ import path from 'path'; import _ from 'lodash'; import { fs, util, tempDir } from '@appium/support'; import { EOL } from 'os'; -import Logcat from '../logcat'; +import { Logcat } from '../logcat'; import { sleep, waitForCondition } from 'asyncbox'; import { SubProcess, exec } from 'teen_process'; import B from 'bluebird'; @@ -34,8 +34,6 @@ const PROCESS_NAME_COLUMN_TITLE = 'NAME'; const PS_TITLE_PATTERN = new RegExp(`^(.*\\b${PID_COLUMN_TITLE}\\b.*\\b${PROCESS_NAME_COLUMN_TITLE}\\b.*)$`, 'm'); const MIN_API_LEVEL_WITH_PERMS_SUPPORT = 23; -const methods = {}; - /** * Creates chunks for the given arguments and executes them in `adb shell`. * This is faster than calling `adb shell` separately for each arg, however @@ -51,7 +49,7 @@ const methods = {}; * @param {string[]} args Array of argument values to create chunks for * @throws {Error} If any of the chunks returns non-zero exit code after being executed */ -methods.shellChunks = async function shellChunks (argTransformer, args) { +export async function shellChunks (argTransformer, args) { const commands = []; /** @type {string[]} */ let cmdChunk = []; @@ -84,7 +82,7 @@ methods.shellChunks = async function shellChunks (argTransformer, args) { if (lastError) { throw lastError; } -}; +} /** * Get the path to adb executable amd assign it @@ -93,52 +91,52 @@ methods.shellChunks = async function shellChunks (argTransformer, args) { * @this {import('../adb.js').ADB} * @return {Promise} ADB instance. */ -methods.getAdbWithCorrectAdbPath = async function getAdbWithCorrectAdbPath () { +export 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} */ -methods.initAapt = async function initAapt () { +export 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} */ -methods.initAapt2 = async function initAapt2 () { +export 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} */ -methods.initZipAlign = async function initZipAlign () { +export 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} */ -methods.initBundletool = async function initBundletool () { +export async function initBundletool () { try { (/** @type {import('@appium/types').StringRecord} */(this.binaries)).bundletool = await fs.which('bundletool.jar'); - } catch (err) { + } 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. @@ -148,7 +146,7 @@ methods.initBundletool = async function initBundletool () { * Android Lollipop. The result of this method is cached, so all the further * calls return the same value as the first one. */ -methods.getApiLevel = async function getApiLevel () { +export async function getApiLevel () { if (!_.isInteger(this._apiLevel)) { try { const strOutput = await this.getDeviceProperty('ro.build.version.sdk'); @@ -176,7 +174,7 @@ methods.getApiLevel = async function getApiLevel () { } } return /** @type {number} */(this._apiLevel); -}; +} /** * Retrieve the platform version of the device under test. @@ -185,7 +183,7 @@ methods.getApiLevel = async function getApiLevel () { * @return {Promise} The platform version as a string, for example '5.0' for * Android Lollipop. */ -methods.getPlatformVersion = async function getPlatformVersion () { +export async function getPlatformVersion () { log.info('Getting device platform version'); try { return await this.getDeviceProperty('ro.build.version.release'); @@ -195,7 +193,7 @@ methods.getPlatformVersion = async function getPlatformVersion () { `Original error: ${(/**@type {Error} */ (e)).message}` ); } -}; +} /** * Verify whether a device is connected. @@ -203,10 +201,10 @@ methods.getPlatformVersion = async function getPlatformVersion () { * @this {import('../adb.js').ADB} * @return {Promise} True if at least one device is visible to adb. */ -methods.isDeviceConnected = async function isDeviceConnected () { +export async function isDeviceConnected () { let devices = await this.getConnectedDevices(); return devices.length > 0; -}; +} /** * Recursively create a new folder on the device under test. @@ -215,9 +213,9 @@ methods.isDeviceConnected = async function isDeviceConnected () { * @param {string} remotePath - The new path to be created. * @return {Promise} mkdir command output. */ -methods.mkdir = async function mkdir (remotePath) { +export async function mkdir (remotePath) { return await this.shell(['mkdir', '-p', remotePath]); -}; +} /** * Verify whether the given argument is a @@ -228,10 +226,10 @@ methods.mkdir = async function mkdir (remotePath) { * @return {boolean} The result of Regexp.exec operation * or _null_ if no matches are found. */ -methods.isValidClass = function isValidClass (classString) { +export function isValidClass (classString) { // some.package/some.package.Activity return !!matchComponentName(classString); -}; +} /** * @typedef {Object} ResolveActivityOptions @@ -256,7 +254,7 @@ methods.isValidClass = function isValidClass (classString) { * @return {Promise} Fully qualified name of the launchable activity * @throws {Error} If there was an error while resolving the activity name */ -methods.resolveLaunchableActivity = async function resolveLaunchableActivity (pkg, opts = {}) { +export async function resolveLaunchableActivity (pkg, opts = {}) { const { preferCmd = true } = opts; if (!preferCmd || await this.getApiLevel() < 24) { const stdout = await this.shell(['dumpsys', 'package', pkg]); @@ -296,7 +294,7 @@ methods.resolveLaunchableActivity = async function resolveLaunchableActivity (pk throw new Error( `Unable to resolve the launchable activity of '${pkg}'. Original error: ${stderr || stdout}` ); -}; +} /** * Force application to stop on the device under test. @@ -305,9 +303,9 @@ methods.resolveLaunchableActivity = async function resolveLaunchableActivity (pk * @param {string} pkg - The package name to be stopped. * @return {Promise} The output of the corresponding adb command. */ -methods.forceStop = async function forceStop (pkg) { +export async function forceStop (pkg) { return await this.shell(['am', 'force-stop', pkg]); -}; +} /** * Kill application @@ -316,9 +314,9 @@ methods.forceStop = async function forceStop (pkg) { * @param {string} pkg - The package name to be stopped. * @return {Promise} The output of the corresponding adb command. */ -methods.killPackage = async function killPackage (pkg) { +export async function killPackage (pkg) { return await this.shell(['am', 'kill', pkg]); -}; +} /** * Clear the user data of the particular application on the device @@ -328,9 +326,9 @@ methods.killPackage = async function killPackage (pkg) { * @param {string} pkg - The package name to be cleared. * @return {Promise} The output of the corresponding adb command. */ -methods.clear = async function clear (pkg) { +export async function clear (pkg) { return await this.shell(['pm', 'clear', pkg]); -}; +} /** * Grant all permissions requested by the particular package. @@ -342,7 +340,7 @@ methods.clear = async function clear (pkg) { * @param {string} [apk] - The path to the actual apk file. * @throws {Error} If there was an error while granting permissions */ -methods.grantAllPermissions = async function grantAllPermissions (pkg, apk) { +export async function grantAllPermissions (pkg, apk) { const apiLevel = await this.getApiLevel(); let targetSdk = 0; let dumpsysOutput = null; @@ -357,7 +355,7 @@ methods.grantAllPermissions = async function grantAllPermissions (pkg, apk) { } else { targetSdk = await this.targetSdkVersionFromManifest(apk); } - } catch (e) { + } catch { //avoiding logging error stack, as calling library function would have logged log.warn(`Ran into problem getting target SDK version; ignoring...`); } @@ -384,7 +382,7 @@ methods.grantAllPermissions = async function grantAllPermissions (pkg, apk) { log.info(`The device's OS API level is ${apiLevel}. ` + `It is only possible to grant permissions on devices running Android 6 or above.`); } -}; +} /** * Grant multiple permissions for the particular package. @@ -396,7 +394,7 @@ methods.grantAllPermissions = async function grantAllPermissions (pkg, apk) { * @param {Array} permissions - The list of permissions to be granted. * @throws {Error} If there was an error while changing permissions. */ -methods.grantPermissions = async function grantPermissions (pkg, permissions) { +export async function grantPermissions (pkg, permissions) { // As it consumes more time for granting each permission, // trying to grant all permission by forming equivalent command. // Also, it is necessary to split long commands into chunks, since the maximum length of @@ -410,7 +408,7 @@ methods.grantPermissions = async function grantPermissions (pkg, permissions) { throw err; } } -}; +} /** * Grant single permission for the particular package. @@ -420,7 +418,7 @@ methods.grantPermissions = async function grantPermissions (pkg, permissions) { * @param {string} permission - The full name of the permission to be granted. * @throws {Error} If there was an error while changing permissions. */ -methods.grantPermission = async function grantPermission (pkg, permission) { +export async function grantPermission (pkg, permission) { try { await this.shell(['pm', 'grant', pkg, permission]); } catch (e) { @@ -429,7 +427,7 @@ methods.grantPermission = async function grantPermission (pkg, permission) { throw err; } } -}; +} /** * Revoke single permission from the particular package. @@ -439,7 +437,7 @@ methods.grantPermission = async function grantPermission (pkg, permission) { * @param {string} permission - The full name of the permission to be revoked. * @throws {Error} If there was an error while changing permissions. */ -methods.revokePermission = async function revokePermission (pkg, permission) { +export async function revokePermission (pkg, permission) { try { await this.shell(['pm', 'revoke', pkg, permission]); } catch (e) { @@ -448,7 +446,7 @@ methods.revokePermission = async function revokePermission (pkg, permission) { throw err; } } -}; +} /** * Retrieve the list of granted permissions for the particular package. @@ -460,11 +458,11 @@ methods.revokePermission = async function revokePermission (pkg, permission) { * @return {Promise} The list of granted permissions or an empty list. * @throws {Error} If there was an error while changing permissions. */ -methods.getGrantedPermissions = async function getGrantedPermissions (pkg, cmdOutput = null) { +export async function getGrantedPermissions (pkg, cmdOutput = null) { log.debug('Retrieving granted permissions'); const stdout = cmdOutput || await this.shell(['dumpsys', 'package', pkg]); return extractMatchingPermissions(stdout, ['install', 'runtime'], true); -}; +} /** * Retrieve the list of denied permissions for the particular package. @@ -475,11 +473,11 @@ methods.getGrantedPermissions = async function getGrantedPermissions (pkg, cmdOu * _dumpsys package_ command. It may speed up the method execution. * @return {Promise} The list of denied permissions or an empty list. */ -methods.getDeniedPermissions = async function getDeniedPermissions (pkg, cmdOutput = null) { +export async function getDeniedPermissions (pkg, cmdOutput = null) { log.debug('Retrieving denied permissions'); const stdout = cmdOutput || await this.shell(['dumpsys', 'package', pkg]); return extractMatchingPermissions(stdout, ['install', 'runtime'], false); -}; +} /** * Retrieve the list of requested permissions for the particular package. @@ -490,11 +488,11 @@ methods.getDeniedPermissions = async function getDeniedPermissions (pkg, cmdOutp * _dumpsys package_ command. It may speed up the method execution. * @return {Promise} The list of requested permissions or an empty list. */ -methods.getReqPermissions = async function getReqPermissions (pkg, cmdOutput = null) { +export async function getReqPermissions (pkg, cmdOutput = null) { log.debug('Retrieving requested permissions'); const stdout = cmdOutput || await this.shell(['dumpsys', 'package', pkg]); return extractMatchingPermissions(stdout, ['requested']); -}; +} /** * Retrieve the list of location providers for the device under test. @@ -502,7 +500,7 @@ methods.getReqPermissions = async function getReqPermissions (pkg, cmdOutput = n * @this {import('../adb.js').ADB} * @return {Promise} The list of available location providers or an empty list. */ -methods.getLocationProviders = async function getLocationProviders () { +export async function getLocationProviders () { if (await this.getApiLevel() < 31) { // https://stackoverflow.com/questions/70939503/settings-secure-location-providers-allowed-returns-null-in-android-12 const stdout = await this.getSetting('secure', 'location_providers_allowed'); @@ -515,7 +513,7 @@ methods.getLocationProviders = async function getLocationProviders () { return _.includes(await this.shell(['cmd', 'location', 'is-location-enabled']), 'true') ? ['gps'] : []; -}; +} /** * Toggle the state of GPS location provider. @@ -523,14 +521,14 @@ methods.getLocationProviders = async function getLocationProviders () { * @this {import('../adb.js').ADB} * @param {boolean} enabled - Whether to enable (true) or disable (false) the GPS provider. */ -methods.toggleGPSLocationProvider = async function toggleGPSLocationProvider (enabled) { +export async function toggleGPSLocationProvider (enabled) { if (await this.getApiLevel() < 31) { // https://stackoverflow.com/questions/70939503/settings-secure-location-providers-allowed-returns-null-in-android-12 await this.setSetting('secure', 'location_providers_allowed', `${enabled ? '+' : '-'}gps`); return; } await this.shell(['cmd', 'location', 'set-location-enabled', enabled ? 'true' : 'false']); -}; +} /** * Decorates an exception message with a solution link @@ -572,7 +570,7 @@ function decorateWriteSecureSettingsException (e) { * @throws {error} If there was an error and ignoreError was true while executing 'adb shell settings put global' * command on the device under test. */ -methods.setHiddenApiPolicy = async function setHiddenApiPolicy (value, ignoreError = false) { +export async function setHiddenApiPolicy (value, ignoreError = false) { try { await this.shell(HIDDEN_API_POLICY_KEYS.map((k) => `settings put global ${k} ${value}`).join(';')); } catch (e) { @@ -585,7 +583,7 @@ methods.setHiddenApiPolicy = async function setHiddenApiPolicy (value, ignoreErr `Original error: ${err.message}` ); } -}; +} /** * Reset access to non-SDK APIs to its default setting. @@ -596,7 +594,7 @@ methods.setHiddenApiPolicy = async function setHiddenApiPolicy (value, ignoreErr * @throws {error} If there was an error and ignoreError was true while executing 'adb shell settings delete global' * command on the device under test. */ -methods.setDefaultHiddenApiPolicy = async function setDefaultHiddenApiPolicy (ignoreError = false) { +export async function setDefaultHiddenApiPolicy (ignoreError = false) { try { await this.shell(HIDDEN_API_POLICY_KEYS.map((k) => `settings delete global ${k}`).join(';')); } catch (e) { @@ -606,7 +604,7 @@ methods.setDefaultHiddenApiPolicy = async function setDefaultHiddenApiPolicy (ig } log.info(`Failed to delete keys '${HIDDEN_API_POLICY_KEYS}'. Original error: ${err.message}`); } -}; +} /** * Stop the particular package if it is running and clears its application data. @@ -614,7 +612,7 @@ methods.setDefaultHiddenApiPolicy = async function setDefaultHiddenApiPolicy (ig * @this {import('../adb.js').ADB} * @param {string} pkg - The package name to be processed. */ -methods.stopAndClear = async function stopAndClear (pkg) { +export async function stopAndClear (pkg) { try { await this.forceStop(pkg); await this.clear(pkg); @@ -622,7 +620,7 @@ methods.stopAndClear = async function stopAndClear (pkg) { const err = /** @type {Error} */ (e); throw new Error(`Cannot stop and clear ${pkg}. Original error: ${err.message}`); } -}; +} /** * Retrieve the list of available input methods (IMEs) for the device under test. @@ -630,14 +628,14 @@ methods.stopAndClear = async function stopAndClear (pkg) { * @this {import('../adb.js').ADB} * @return {Promise} The list of IME names or an empty list. */ -methods.availableIMEs = async function availableIMEs () { +export async function availableIMEs () { try { return getIMEListFromOutput(await this.shell(['ime', 'list', '-a'])); } catch (e) { const err = /** @type {Error} */ (e); throw new Error(`Error getting available IME's. Original error: ${err.message}`); } -}; +} /** * Retrieve the list of enabled input methods (IMEs) for the device under test. @@ -645,14 +643,14 @@ methods.availableIMEs = async function availableIMEs () { * @this {import('../adb.js').ADB} * @return {Promise} The list of enabled IME names or an empty list. */ -methods.enabledIMEs = async function enabledIMEs () { +export async function enabledIMEs () { try { return getIMEListFromOutput(await this.shell(['ime', 'list'])); } catch (e) { const err = /** @type {Error} */ (e); throw new Error(`Error getting enabled IME's. Original error: ${err.message}`); } -}; +} /** * Enable the particular input method on the device under test. @@ -660,9 +658,9 @@ methods.enabledIMEs = async function enabledIMEs () { * @this {import('../adb.js').ADB} * @param {string} imeId - One of existing IME ids. */ -methods.enableIME = async function enableIME (imeId) { +export async function enableIME (imeId) { await this.shell(['ime', 'enable', imeId]); -}; +} /** * Disable the particular input method on the device under test. @@ -670,9 +668,9 @@ methods.enableIME = async function enableIME (imeId) { * @this {import('../adb.js').ADB} * @param {string} imeId - One of existing IME ids. */ -methods.disableIME = async function disableIME (imeId) { +export async function disableIME (imeId) { await this.shell(['ime', 'disable', imeId]); -}; +} /** * Set the particular input method on the device under test. @@ -680,9 +678,9 @@ methods.disableIME = async function disableIME (imeId) { * @this {import('../adb.js').ADB} * @param {string} imeId - One of existing IME ids. */ -methods.setIME = async function setIME (imeId) { +export async function setIME (imeId) { await this.shell(['ime', 'set', imeId]); -}; +} /** * Get the default input method on the device under test. @@ -690,7 +688,7 @@ methods.setIME = async function setIME (imeId) { * @this {import('../adb.js').ADB} * @return {Promise} The name of the default input method */ -methods.defaultIME = async function defaultIME () { +export async function defaultIME () { try { let engine = await this.getSetting('secure', 'default_input_method'); if (engine === 'null') { @@ -701,7 +699,7 @@ methods.defaultIME = async function defaultIME () { const err = /** @type {Error} */ (e); throw new Error(`Error getting default IME. Original error: ${err.message}`); } -}; +} /** * Send the particular keycode to the device under test. @@ -709,11 +707,11 @@ methods.defaultIME = async function defaultIME () { * @this {import('../adb.js').ADB} * @param {string|number} keycode - The actual key code to be sent. */ -methods.keyevent = async function keyevent (keycode) { +export async function keyevent (keycode) { // keycode must be an int. const code = parseInt(`${keycode}`, 10); await this.shell(['input', 'keyevent', `${code}`]); -}; +} /** * Send the particular text or a number to the device under test. @@ -724,7 +722,7 @@ methods.keyevent = async function keyevent (keycode) { * @param {string|number} text - The actual text to be sent. * @throws {Error} If it is impossible to escape the given string */ -methods.inputText = async function inputText (text) { +export async function inputText (text) { if (text === '') { return; } @@ -744,7 +742,7 @@ methods.inputText = async function inputText (text) { args = [`input text ${q}${escapedText}${q}`]; } await this.shell(args); -}; +} /** * Clear the active text field on the device under test by sending @@ -753,7 +751,7 @@ methods.inputText = async function inputText (text) { * @this {import('../adb.js').ADB} * @param {number} [length=100] - The maximum length of the text in the field to be cleared. */ -methods.clearTextField = async function clearTextField (length = 100) { +export async function clearTextField (length = 100) { // assumes that the EditText field already has focus log.debug(`Clearing up to ${length} characters`); if (length === 0) { @@ -768,13 +766,13 @@ methods.clearTextField = async function clearTextField (length = 100) { args.push('67', '112'); } await this.shell(args); -}; +} /** * Send the special keycode to the device under test in order to lock it. * @this {import('../adb.js').ADB} */ -methods.lock = async function lock () { +export async function lock () { if (await this.isScreenLocked()) { log.debug('Screen is already locked. Doing nothing.'); return; @@ -788,38 +786,38 @@ methods.lock = async function lock () { waitMs: timeoutMs, intervalMs: 500, }); - } catch (e) { + } catch { throw new Error(`The device screen is still not locked after ${timeoutMs}ms timeout`); } -}; +} /** * Send the special keycode to the device under test in order to emulate * Back button tap. * @this {import('../adb.js').ADB} */ -methods.back = async function back () { +export async function back () { 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} */ -methods.goToHome = async function goToHome () { +export async function goToHome () { log.debug('Pressing the HOME button'); await this.keyevent(3); -}; +} /** * @this {import('../adb.js').ADB} * @return {string} the actual path to adb executable. */ -methods.getAdbPath = function getAdbPath () { +export function getAdbPath () { return this.executable.path; -}; +} /** * Retrieve current screen orientation of the device under test. @@ -827,10 +825,10 @@ methods.getAdbPath = function getAdbPath () { * @this {import('../adb.js').ADB} * @return {Promise} The current orientation encoded as an integer number. */ -methods.getScreenOrientation = async function getScreenOrientation () { +export async function getScreenOrientation () { let stdout = await this.shell(['dumpsys', 'input']); return getSurfaceOrientation(stdout); -}; +} /** * Send an arbitrary Telnet command to the device under test. @@ -839,9 +837,9 @@ methods.getScreenOrientation = async function getScreenOrientation () { * @param {string} command - The command to be sent. * @return {Promise} The actual output of the given command. */ -methods.sendTelnetCommand = async function sendTelnetCommand (command) { +export async function sendTelnetCommand (command) { return await this.execEmuConsoleCommand(command, {port: await this.getEmulatorPort()}); -}; +} /** * Check the state of Airplane mode on the device under test. @@ -849,12 +847,12 @@ methods.sendTelnetCommand = async function sendTelnetCommand (command) { * @this {import('../adb.js').ADB} * @return {Promise} True if Airplane mode is enabled. */ -methods.isAirplaneModeOn = async function isAirplaneModeOn () { +export async function isAirplaneModeOn () { const stdout = await this.getSetting('global', 'airplane_mode_on'); return parseInt(stdout, 10) !== 0; // Alternatively for Android 11+: // return (await this.shell(['cmd', 'connectivity', 'airplane-mode'])).stdout.trim() === 'enabled'; -}; +} /** * Change the state of Airplane mode in Settings on the device under test. @@ -862,7 +860,7 @@ methods.isAirplaneModeOn = async function isAirplaneModeOn () { * @this {import('../adb.js').ADB} * @param {boolean} on - True to enable the Airplane mode in Settings and false to disable it. */ -methods.setAirplaneMode = async function setAirplaneMode (on) { +export async function setAirplaneMode (on) { if (await this.getApiLevel() < 30) { // This requires to call broadcastAirplaneMode afterwards to apply await this.setSetting('global', 'airplane_mode_on', on ? 1 : 0); @@ -870,7 +868,7 @@ methods.setAirplaneMode = async function setAirplaneMode (on) { } await this.shell(['cmd', 'connectivity', 'airplane-mode', on ? 'enable' : 'disable']); -}; +} /** * Change the state of the bluetooth service on the device under test. @@ -878,13 +876,13 @@ methods.setAirplaneMode = async function setAirplaneMode (on) { * @this {import('../adb.js').ADB} * @param {boolean} on - True to enable bluetooth service and false to disable it. */ -methods.setBluetoothOn = async function setBluetoothOn (on) { +export async function setBluetoothOn (on) { if (await this.getApiLevel() < 30) { throw new Error('Changing of the bluetooth state is not supported on your device'); } await this.shell(['cmd', 'bluetooth_manager', on ? 'enable' : 'disable']); -}; +} /** * Change the state of the NFC service on the device under test. @@ -893,7 +891,7 @@ methods.setBluetoothOn = async function setBluetoothOn (on) { * @param {boolean} on - True to enable NFC service and false to disable it. * @throws {Error} If there was an error while changing the service state */ -methods.setNfcOn = async function setNfcOn (on) { +export async function setNfcOn (on) { const {stdout, stderr} = await this.shell(['svc', 'nfc', on ? 'enable' : 'disable'], { outputFormat: 'full' }); @@ -904,7 +902,7 @@ methods.setNfcOn = async function setNfcOn (on) { `Cannot turn ${on ? 'on' : 'off'} the NFC adapter. Does the device under test have it?` ); } -}; +} /** * Broadcast the state of Airplane mode on the device under test. @@ -917,7 +915,7 @@ methods.setNfcOn = async function setNfcOn (on) { * @this {import('../adb.js').ADB} * @param {boolean} on - True to broadcast enable and false to broadcast disable. */ -methods.broadcastAirplaneMode = async function broadcastAirplaneMode (on) { +export async function broadcastAirplaneMode (on) { const args = [ 'am', 'broadcast', '-a', 'android.intent.action.AIRPLANE_MODE', @@ -932,11 +930,11 @@ methods.broadcastAirplaneMode = async function broadcastAirplaneMode (on) { try { await this.shell(args, {privileged: true}); return; - } catch (ign) {} + } catch {} } throw err; } -}; +} /** * Check the state of WiFi on the device under test. @@ -944,12 +942,12 @@ methods.broadcastAirplaneMode = async function broadcastAirplaneMode (on) { * @this {import('../adb.js').ADB} * @return {Promise} True if WiFi is enabled. */ -methods.isWifiOn = async function isWifiOn () { +export async function isWifiOn () { const stdout = await this.getSetting('global', 'wifi_on'); return (parseInt(stdout, 10) !== 0); // Alternative for Android 11+: // return (await this.shell(['cmd', 'wifi', 'status']).stdout.includes('Wifi is enabled')); -}; +} /** * Check the state of Data transfer on the device under test. @@ -957,10 +955,10 @@ methods.isWifiOn = async function isWifiOn () { * @this {import('../adb.js').ADB} * @return {Promise} True if Data transfer is enabled. */ -methods.isDataOn = async function isDataOn () { +export async function isDataOn () { const stdout = await this.getSetting('global', 'mobile_data'); return (parseInt(stdout, 10) !== 0); -}; +} /** * Check the state of animation on the device under test below: @@ -972,11 +970,11 @@ methods.isDataOn = async function isDataOn () { * @return {Promise} True if at least one of animation scale settings * is not equal to '0.0'. */ -methods.isAnimationOn = async function isAnimationOn () { +export async function isAnimationOn () { return (await B.all(ANIMATION_SCALE_KEYS.map( async (k) => (await this.getSetting('global', k)) !== '0.0')) ).includes(true); -}; +} /** * Set animation scale with the given value via adb shell settings command. @@ -995,9 +993,9 @@ methods.isAnimationOn = async function isAnimationOn () { * @return {Promise} * @throws {Error} If the adb setting command raises an exception. */ -methods.setAnimationScale = async function setAnimationScale (value) { +export async function setAnimationScale (value) { await B.all(ANIMATION_SCALE_KEYS.map((k) => this.setSetting('global', k, value))); -}; +} /** * Forcefully recursively remove a path on the device under test. @@ -1006,9 +1004,9 @@ methods.setAnimationScale = async function setAnimationScale (value) { * @this {import('../adb.js').ADB} * @param {string} path - The path to be removed recursively. */ -methods.rimraf = async function rimraf (path) { +export async function rimraf (path) { await this.shell(['rm', '-rf', path]); -}; +} /** * Send a file to the device under test. @@ -1021,10 +1019,10 @@ methods.rimraf = async function rimraf (path) { * _exec_ method options, for more information about available * options. */ -methods.push = async function push (localPath, remotePath, opts) { +export async function push (localPath, remotePath, opts) { await this.mkdir(path.posix.dirname(remotePath)); await this.adbExec(['push', localPath, remotePath], opts); -}; +} /** * Receive a file from the device under test. @@ -1037,10 +1035,10 @@ methods.push = async function push (localPath, remotePath, opts) { * _exec_ method options, for more information about available * options. */ -methods.pull = async function pull (remotePath, localPath, opts = {}) { +export async function pull (remotePath, localPath, opts = {}) { // pull folder can take more time, increasing time out to 60 secs await this.adbExec(['pull', remotePath, localPath], {...opts, timeout: opts.timeout ?? 60000}); -}; +} /** * Check whether the process with the particular name is running on the device @@ -1051,9 +1049,9 @@ methods.pull = async function pull (remotePath, localPath, opts = {}) { * @return {Promise} True if the given process is running. * @throws {Error} If the given process name is not a valid class name. */ -methods.processExists = async function processExists (processName) { +export async function processExists (processName) { return !_.isEmpty(await this.getPIDsByName(processName)); -}; +} /** * Get TCP port forwarding with adb on the device under test. @@ -1062,11 +1060,11 @@ methods.processExists = async function processExists (processName) { * @return {Promise} The output of the corresponding adb command. * An array contains each forwarding line of output */ -methods.getForwardList = async function getForwardList () { +export async function getForwardList () { log.debug(`List forwarding ports`); const connections = await this.adbExec(['forward', '--list']); return connections.split(EOL).filter((line) => Boolean(line.trim())); -}; +} /** * Setup TCP port forwarding with adb on the device under test. @@ -1075,10 +1073,10 @@ methods.getForwardList = async function getForwardList () { * @param {string|number} systemPort - The number of the local system port. * @param {string|number} devicePort - The number of the remote device port. */ -methods.forwardPort = async function forwardPort (systemPort, devicePort) { +export async function forwardPort (systemPort, devicePort) { log.debug(`Forwarding system: ${systemPort} to device: ${devicePort}`); await this.adbExec(['forward', `tcp:${systemPort}`, `tcp:${devicePort}`]); -}; +} /** * Remove TCP port forwarding with adb on the device under test. The forwarding @@ -1088,10 +1086,10 @@ methods.forwardPort = async function forwardPort (systemPort, devicePort) { * @param {string|number} systemPort - The number of the local system port * to remove forwarding on. */ -methods.removePortForward = async function removePortForward (systemPort) { +export async function removePortForward (systemPort) { log.debug(`Removing forwarded port socket connection: ${systemPort} `); await this.adbExec(['forward', `--remove`, `tcp:${systemPort}`]); -}; +} /** * Get TCP port forwarding with adb on the device under test. @@ -1100,11 +1098,11 @@ methods.removePortForward = async function removePortForward (systemPort) { * @return {Promise} The output of the corresponding adb command. * An array contains each forwarding line of output */ -methods.getReverseList = async function getReverseList () { +export async function getReverseList () { log.debug(`List reverse forwarding ports`); const connections = await this.adbExec(['reverse', '--list']); return connections.split(EOL).filter((line) => Boolean(line.trim())); -}; +} /** * Setup TCP port forwarding with adb on the device under test. @@ -1114,10 +1112,10 @@ methods.getReverseList = async function getReverseList () { * @param {string|number} devicePort - The number of the remote device port. * @param {string|number} systemPort - The number of the local system port. */ -methods.reversePort = async function reversePort (devicePort, systemPort) { +export async function reversePort (devicePort, systemPort) { log.debug(`Forwarding device: ${devicePort} to system: ${systemPort}`); await this.adbExec(['reverse', `tcp:${devicePort}`, `tcp:${systemPort}`]); -}; +} /** * Remove TCP port forwarding with adb on the device under test. The forwarding @@ -1127,10 +1125,10 @@ methods.reversePort = async function reversePort (devicePort, systemPort) { * @param {string|number} devicePort - The number of the remote device port * to remove forwarding on. */ -methods.removePortReverse = async function removePortReverse (devicePort) { +export async function removePortReverse (devicePort) { log.debug(`Removing reverse forwarded port socket connection: ${devicePort} `); await this.adbExec(['reverse', `--remove`, `tcp:${devicePort}`]); -}; +} /** * Setup TCP port forwarding with adb on the device under test. The difference @@ -1141,10 +1139,10 @@ methods.removePortReverse = async function removePortReverse (devicePort) { * @param {string|number} systemPort - The number of the local system port. * @param {string|number} devicePort - The number of the remote device port. */ -methods.forwardAbstractPort = async function forwardAbstractPort (systemPort, devicePort) { +export async function forwardAbstractPort (systemPort, devicePort) { log.debug(`Forwarding system: ${systemPort} to abstract device: ${devicePort}`); await this.adbExec(['forward', `tcp:${systemPort}`, `localabstract:${devicePort}`]); -}; +} /** * Execute ping shell command on the device under test. @@ -1154,13 +1152,13 @@ methods.forwardAbstractPort = async function forwardAbstractPort (systemPort, de * @throws {Error} If there was an error while executing 'ping' command on the * device under test. */ -methods.ping = async function ping () { +export async function ping () { let stdout = await this.shell(['echo', 'ping']); if (stdout.indexOf('ping') === 0) { return true; } throw new Error(`ADB ping failed, returned ${stdout}`); -}; +} /** * Restart the device under test using adb commands. @@ -1168,7 +1166,7 @@ methods.ping = async function ping () { * @this {import('../adb.js').ADB} * @throws {Error} If start fails. */ -methods.restart = async function restart () { +export async function restart () { try { await this.stopLogcat(); await this.restartAdb(); @@ -1178,7 +1176,7 @@ methods.restart = async function restart () { const err = /** @type {Error} */ (e); throw new Error(`Restart failed. Original error: ${err.message}`); } -}; +} /** * Start the logcat process to gather logs. @@ -1187,7 +1185,7 @@ methods.restart = async function restart () { * @param {import('../logcat.js').LogcatOpts} [opts={}] * @throws {Error} If restart fails. */ -methods.startLogcat = async function startLogcat (opts = {}) { +export async function startLogcat (opts = {}) { if (!_.isEmpty(this.logcat)) { throw new Error("Trying to start logcat capture but it's already started!"); } @@ -1200,14 +1198,14 @@ methods.startLogcat = async function startLogcat (opts = {}) { }); await this.logcat.startCapture(opts); this._logcatStartupParams = opts; -}; +} /** * Stop the active logcat process which gathers logs. * The call will be ignored if no logcat process is running. * @this {import('../adb.js').ADB} */ -methods.stopLogcat = async function stopLogcat () { +export async function stopLogcat () { if (_.isEmpty(this.logcat)) { return; } @@ -1216,7 +1214,7 @@ methods.stopLogcat = async function stopLogcat () { } finally { this.logcat = undefined; } -}; +} /** * Retrieve the output from the currently running logcat process. @@ -1226,12 +1224,12 @@ methods.stopLogcat = async function stopLogcat () { * @return {import('../logcat').LogEntry[]} The collected logcat output. * @throws {Error} If logcat process is not running. */ -methods.getLogcatLogs = function getLogcatLogs () { +export function getLogcatLogs () { if (_.isEmpty(this.logcat)) { throw new Error(`Can't get logcat logs since logcat hasn't started`); } return this.logcat.getLogs(); -}; +} /** * Listener function, which accepts one argument. @@ -1255,12 +1253,12 @@ methods.getLogcatLogs = function getLogcatLogs () { * @param {LogcatListener} listener - Listener function * @throws {Error} If logcat process is not running. */ -methods.setLogcatListener = function setLogcatListener (listener) { +export function setLogcatListener (listener) { if (_.isEmpty(this.logcat)) { throw new Error("Logcat process hasn't been started"); } this.logcat.on('output', listener); -}; +} /** * Removes the previously set callback for the logcat output event. @@ -1270,12 +1268,12 @@ methods.setLogcatListener = function setLogcatListener (listener) { * passed to `setLogcatListener` * @throws {Error} If logcat process is not running. */ -methods.removeLogcatListener = function removeLogcatListener (listener) { +export function removeLogcatListener (listener) { if (_.isEmpty(this.logcat)) { throw new Error("Logcat process hasn't been started"); } this.logcat.removeListener('output', listener); -}; +} /** * At some point of time Google has changed the default `ps` behaviour, so it only @@ -1286,7 +1284,7 @@ methods.removeLogcatListener = function removeLogcatListener (listener) { * @this {import('../adb.js').ADB} * @returns {Promise} the output of `ps` command where all processes are included */ -methods.listProcessStatus = async function listProcessStatus () { +export async function listProcessStatus () { if (!_.isBoolean(this._doesPsSupportAOption)) { try { this._doesPsSupportAOption = /^-A\b/m.test(await this.shell(['ps', '--help'])); @@ -1296,7 +1294,7 @@ methods.listProcessStatus = async function listProcessStatus () { } } return await this.shell(this._doesPsSupportAOption ? ['ps', '-A'] : ['ps']); -}; +} /** * Returns process name for the given process identifier @@ -1307,7 +1305,7 @@ methods.listProcessStatus = async function listProcessStatus () { * in the active processes list * @returns {Promise} The process name */ -methods.getNameByPid = async function getNameByPid (pid) { +export async function getNameByPid (pid) { // @ts-ignore This validation works as expected if (isNaN(pid)) { throw new Error(`The PID value must be a valid number. '${pid}' is given instead`); @@ -1337,7 +1335,7 @@ methods.getNameByPid = async function getNameByPid (pid) { } log.debug(stdout); throw new Error(`Could not get the process name for PID '${pid}'`); -}; +} /** * Get the list of process ids for the particular process on the device under test. @@ -1347,7 +1345,7 @@ methods.getNameByPid = async function getNameByPid (pid) { * @return {Promise} The list of matched process IDs or an empty list. * @throws {Error} If the passed process name is not a valid one */ -methods.getPIDsByName = async function getPIDsByName (name) { +export async function getPIDsByName (name) { log.debug(`Getting IDs of all '${name}' processes`); if (!this.isValidClass(name)) { throw new Error(`Invalid process name: '${name}'`); @@ -1415,7 +1413,7 @@ methods.getPIDsByName = async function getPIDsByName (name) { pids.push(parseInt(items[pidIndex], 10)); } return pids; -}; +} /** * Get the list of process ids for the particular process on the device under test. @@ -1423,7 +1421,7 @@ methods.getPIDsByName = async function getPIDsByName (name) { * @this {import('../adb.js').ADB} * @param {string} name - The part of process name. */ -methods.killProcessesByName = async function killProcessesByName (name) { +export async function killProcessesByName (name) { try { log.debug(`Attempting to kill all ${name} processes`); const pids = await this.getPIDsByName(name); @@ -1436,7 +1434,7 @@ methods.killProcessesByName = async function killProcessesByName (name) { const err = /** @type {Error} */ (e); throw new Error(`Unable to kill ${name} processes. Original error: ${err.message}`); } -}; +} /** * Kill the particular process on the device under test. @@ -1447,7 +1445,7 @@ methods.killProcessesByName = async function killProcessesByName (name) { * @param {string|number} pid - The ID of the process to be killed. * @throws {Error} If the process cannot be killed. */ -methods.killProcessByPID = async function killProcessByPID (pid) { +export async function killProcessByPID (pid) { log.debug(`Attempting to kill process ${pid}`); const noProcessFlag = 'No such process'; try { @@ -1474,7 +1472,7 @@ methods.killProcessByPID = async function killProcessByPID (pid) { throw err1; } } -}; +} /** * Broadcast process killing on the device under test. @@ -1484,7 +1482,7 @@ methods.killProcessByPID = async function killProcessByPID (pid) { * @param {string} processName - The name of the killed process. * @throws {error} If the process was not killed. */ -methods.broadcastProcessEnd = async function broadcastProcessEnd (intent, processName) { +export async function broadcastProcessEnd (intent, processName) { // start the broadcast without waiting for it to finish. this.broadcast(intent); // wait for the process to end @@ -1504,7 +1502,7 @@ methods.broadcastProcessEnd = async function broadcastProcessEnd (intent, proces const err = /** @type {Error} */ (e); throw new Error(`Unable to broadcast process end. Original error: ${err.message}`); } -}; +} /** * Broadcast a message to the given intent. @@ -1513,13 +1511,13 @@ methods.broadcastProcessEnd = async function broadcastProcessEnd (intent, proces * @param {string} intent - The name of the intent to broadcast to. * @throws {error} If intent name is not a valid class name. */ -methods.broadcast = async function broadcast (intent) { +export async function broadcast (intent) { if (!this.isValidClass(intent)) { throw new Error(`Invalid intent ${intent}`); } log.debug(`Broadcasting: ${intent}`); await this.shell(['am', 'broadcast', '-a', intent]); -}; +} /** * Get the particular property of the device under test. @@ -1530,12 +1528,12 @@ methods.broadcast = async function broadcast (intent) { * * @return {Promise} The value of the given property. */ -methods.getDeviceProperty = async function getDeviceProperty (property) { +export async function getDeviceProperty (property) { let stdout = await this.shell(['getprop', property]); let val = stdout.trim(); log.debug(`Current device property '${property}': ${val}`); return val; -}; +} /** * @typedef {Object} SetPropOpts @@ -1553,77 +1551,77 @@ methods.getDeviceProperty = async function getDeviceProperty (property) { * * @throws {error} If _setprop_ utility fails to change property value. */ -methods.setDeviceProperty = async function setDeviceProperty (prop, val, opts = {}) { +export async function setDeviceProperty (prop, val, opts = {}) { const {privileged = true} = opts; log.debug(`Setting device property '${prop}' to '${val}'`); await this.shell(['setprop', prop, val], { privileged, }); -}; +} /** * @this {import('../adb.js').ADB} * @return {Promise} Current system language on the device under test. */ -methods.getDeviceSysLanguage = async function getDeviceSysLanguage () { +export async function getDeviceSysLanguage () { return await this.getDeviceProperty('persist.sys.language'); -}; +} /** * @this {import('../adb.js').ADB} * @return {Promise} Current country name on the device under test. */ -methods.getDeviceSysCountry = async function getDeviceSysCountry () { +export async function getDeviceSysCountry () { return await this.getDeviceProperty('persist.sys.country'); -}; +} /** * @this {import('../adb.js').ADB} * @return {Promise} Current system locale name on the device under test. */ -methods.getDeviceSysLocale = async function getDeviceSysLocale () { +export async function getDeviceSysLocale () { return await this.getDeviceProperty('persist.sys.locale'); -}; +} /** * @this {import('../adb.js').ADB} * @return {Promise} Current product language name on the device under test. */ -methods.getDeviceProductLanguage = async function getDeviceProductLanguage () { +export async function getDeviceProductLanguage () { return await this.getDeviceProperty('ro.product.locale.language'); -}; +} /** * @this {import('../adb.js').ADB} * @return {Promise} Current product country name on the device under test. */ -methods.getDeviceProductCountry = async function getDeviceProductCountry () { +export async function getDeviceProductCountry () { return await this.getDeviceProperty('ro.product.locale.region'); -}; +} /** * @this {import('../adb.js').ADB} * @return {Promise} Current product locale name on the device under test. */ -methods.getDeviceProductLocale = async function getDeviceProductLocale () { +export async function getDeviceProductLocale () { return await this.getDeviceProperty('ro.product.locale'); -}; +} /** * @this {import('../adb.js').ADB} * @return {Promise} The model name of the device under test. */ -methods.getModel = async function getModel () { +export async function getModel () { return await this.getDeviceProperty('ro.product.model'); -}; +} /** * @this {import('../adb.js').ADB} * @return {Promise} The manufacturer name of the device under test. */ -methods.getManufacturer = async function getManufacturer () { +export async function getManufacturer () { return await this.getDeviceProperty('ro.product.manufacturer'); -}; +} /** * Get the current screen size. @@ -1632,14 +1630,14 @@ methods.getManufacturer = async function getManufacturer () { * @return {Promise} Device screen size as string in format 'WxH' or * _null_ if it cannot be determined. */ -methods.getScreenSize = async function getScreenSize () { +export async function getScreenSize () { let stdout = await this.shell(['wm', 'size']); let size = new RegExp(/Physical size: ([^\r?\n]+)*/g).exec(stdout); if (size && size.length >= 2) { return size[1].trim(); } return null; -}; +} /** * Get the current screen density in dpi @@ -1648,7 +1646,7 @@ methods.getScreenSize = async function getScreenSize () { * @return {Promise} Device screen density as a number or _null_ if it * cannot be determined */ -methods.getScreenDensity = async function getScreenDensity () { +export async function getScreenDensity () { let stdout = await this.shell(['wm', 'density']); let density = new RegExp(/Physical density: ([^\r?\n]+)*/g).exec(stdout); if (density && density.length >= 2) { @@ -1656,7 +1654,7 @@ methods.getScreenDensity = async function getScreenDensity () { return isNaN(densityNumber) ? null : densityNumber; } return null; -}; +} /** * Setup HTTP proxy in device global settings. @@ -1666,7 +1664,7 @@ methods.getScreenDensity = async function getScreenDensity () { * @param {string} proxyHost - The host name of the proxy. * @param {string|number} proxyPort - The port number to be set. */ -methods.setHttpProxy = async function setHttpProxy (proxyHost, proxyPort) { +export async function setHttpProxy (proxyHost, proxyPort) { let proxy = `${proxyHost}:${proxyPort}`; if (_.isUndefined(proxyHost)) { throw new Error(`Call to setHttpProxy method with undefined proxy_host: ${proxy}`); @@ -1684,14 +1682,14 @@ methods.setHttpProxy = async function setHttpProxy (proxyHost, proxyPort) { for (const [settingKey, settingValue] of httpProxySettins) { await this.setSetting('global', settingKey, settingValue); } -}; +} /** * Delete HTTP proxy in device global settings. * Rebooting the test device is necessary to apply the change. * @this {import('../adb.js').ADB} */ -methods.deleteHttpProxy = async function deleteHttpProxy () { +export async function deleteHttpProxy () { const httpProxySettins = [ 'http_proxy', 'global_http_proxy_host', @@ -1701,7 +1699,7 @@ methods.deleteHttpProxy = async function deleteHttpProxy () { for (const setting of httpProxySettins) { await this.shell(['settings', 'delete', 'global', setting]); } -}; +} /** * Set device property. @@ -1713,9 +1711,9 @@ methods.deleteHttpProxy = async function deleteHttpProxy () { * @param {string|number} value - property value. * @return {Promise} command output. */ -methods.setSetting = async function setSetting (namespace, setting, value) { +export async function setSetting (namespace, setting, value) { return await this.shell(['settings', 'put', namespace, setting, `${value}`]); -}; +} /** * Get device property. @@ -1726,9 +1724,9 @@ methods.setSetting = async function setSetting (namespace, setting, value) { * @param {string} setting - property name. * @return {Promise} property value. */ -methods.getSetting = async function getSetting (namespace, setting) { +export async function getSetting (namespace, setting) { return await this.shell(['settings', 'get', namespace, setting]); -}; +} /** * Retrieve the `adb bugreport` command output. This @@ -1738,9 +1736,9 @@ methods.getSetting = async function getSetting (namespace, setting) { * @param {number} [timeout=120000] - Command timeout in milliseconds * @returns {Promise} Command stdout */ -methods.bugreport = async function bugreport (timeout = 120000) { +export async function bugreport (timeout = 120000) { return await this.adbExec(['bugreport'], {timeout}); -}; +} /** * @typedef {Object} ScreenrecordOptions @@ -1768,7 +1766,7 @@ methods.bugreport = async function bugreport (timeout = 120000) { * @param {ScreenrecordOptions} [options={}] * @returns {SubProcess} screenrecord process, which can be then controlled by the client code */ -methods.screenrecord = function screenrecord (destination, options = {}) { +export function screenrecord (destination, options = {}) { const cmd = ['screenrecord']; const { videoSize, @@ -1797,7 +1795,7 @@ methods.screenrecord = function screenrecord (destination, options = {}) { ]; log.debug(`Building screenrecord process with the command line: adb ${util.quote(fullCmd)}`); return new SubProcess(this.executable.path, fullCmd); -}; +} /** * Executes the given function with the given input method context @@ -1808,7 +1806,7 @@ methods.screenrecord = function screenrecord (destination, options = {}) { * @param {Function} fn - Function to execute * @returns {Promise} The result of the given function */ -methods.runInImeContext = async function runInImeContext (ime, fn) { +export async function runInImeContext (ime, fn) { const originalIme = await this.defaultIME(); if (originalIme === ime) { log.debug(`The original IME is the same as '${ime}'. There is no need to reset it`); @@ -1825,7 +1823,7 @@ methods.runInImeContext = async function runInImeContext (ime, fn) { await this.setIME(originalIme); } } -}; +} /** * Get tz database time zone formatted timezone @@ -1834,14 +1832,14 @@ methods.runInImeContext = async function runInImeContext (ime, fn) { * @returns {Promise} TZ database Time Zones format * @throws {Error} If any exception is reported by adb shell. */ -methods.getTimeZone = async function getTimeZone () { +export async function getTimeZone () { log.debug('Getting current timezone'); try { return await this.getDeviceProperty('persist.sys.timezone'); } catch (e) { throw new Error(`Error getting timezone. Original error: ${(/** @type {Error} */ (e)).message}`); } -}; +} /** * Retrieves the list of features supported by the device under test @@ -1863,7 +1861,7 @@ methods.getTimeZone = async function getTimeZone () { * ``` * @throws {Error} if there was an error while retrieving the list */ -methods.listFeatures = async function listFeatures () { +export async function listFeatures () { this._memoizedFeatures = this._memoizedFeatures || _.memoize(async () => await this.adbExec(['features']), () => this.curDeviceId); try { @@ -1878,7 +1876,7 @@ methods.listFeatures = async function listFeatures () { } throw err; } -}; +} /** * Checks the state of streamed install feature. @@ -1894,12 +1892,12 @@ methods.listFeatures = async function listFeatures () { * @returns {Promise} `true` if the feature is supported by both adb and the * device under test */ -methods.isStreamedInstallSupported = async function isStreamedInstallSupported () { +export 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. @@ -1910,14 +1908,14 @@ methods.isStreamedInstallSupported = async function isStreamedInstallSupported ( * @returns {Promise} `true` if the feature is supported by both adb and the * device under test */ -methods.isIncrementalInstallSupported = async function isIncrementalInstallSupported () { +export async function isIncrementalInstallSupported () { const {binary} = await this.getVersion(); if (!binary) { return false; } return util.compareVersions(`${binary.version}`, '>=', '30.0.1') && (await this.listFeatures()).includes('abb_exec'); -}; +} /** * Retrieves the list of packages from Doze whitelist on Android 8+ @@ -1928,7 +1926,7 @@ methods.isIncrementalInstallSupported = async function isIncrementalInstallSuppo * system,com.google.android.cellbroadcastreceiver,10143 * user,io.appium.settings,10157 */ -methods.getDeviceIdleWhitelist = async function getDeviceIdleWhitelist () { +export async function getDeviceIdleWhitelist () { if (await this.getApiLevel() < 23) { // Doze mode has only been added since Android 6 return []; @@ -1939,7 +1937,7 @@ methods.getDeviceIdleWhitelist = async function getDeviceIdleWhitelist () { return _.trim(output).split(/\n/) .map((line) => _.trim(line)) .filter(Boolean); -}; +} /** * Adds an existing package(s) into the Doze whitelist on Android 8+ @@ -1951,7 +1949,7 @@ methods.getDeviceIdleWhitelist = async function getDeviceIdleWhitelist () { * will be thrown. * @returns {Promise} `true` if the command to add package(s) has been executed */ -methods.addToDeviceIdleWhitelist = async function addToDeviceIdleWhitelist (...packages) { +export async function addToDeviceIdleWhitelist (...packages) { if (_.isEmpty(packages) || await this.getApiLevel() < 23) { // Doze mode has only been added since Android 6 return false; @@ -1960,7 +1958,7 @@ methods.addToDeviceIdleWhitelist = async function addToDeviceIdleWhitelist (...p log.info(`Adding ${util.pluralize('package', packages.length)} ${JSON.stringify(packages)} to Doze whitelist`); await this.shellChunks((pkg) => ['dumpsys', 'deviceidle', 'whitelist', `+${pkg}`], packages); return true; -}; +} /** * Takes a screenshot of the given display or the default display. @@ -1971,7 +1969,7 @@ methods.addToDeviceIdleWhitelist = async function addToDeviceIdleWhitelist (...p * Note that only recent Android APIs provide multi-screen support. * @returns {Promise} PNG screenshot payload */ -methods.takeScreenshot = async function takeScreenshot (displayId) { +export 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}`; @@ -1996,7 +1994,7 @@ methods.takeScreenshot = async function takeScreenshot (displayId) { throw new Error(`Screenshot of the ${displayDescr} returned no data`); } return stdout; -}; +} /** * Change the state of WiFi on the device under test. @@ -2007,7 +2005,7 @@ methods.takeScreenshot = async function takeScreenshot (displayId) { * @param {boolean} [isEmulator=false] - Set it to true if the device under test * is an emulator rather than a real device. */ -methods.setWifiState = async function setWifiState (on, isEmulator = false) { +export async function setWifiState (on, isEmulator = false) { if (isEmulator) { // The svc command does not require to be root since API 26 await this.shell(['svc', 'wifi', on ? 'enable' : 'disable'], { @@ -2017,7 +2015,7 @@ methods.setWifiState = async function setWifiState (on, isEmulator = false) { } await this.shell(['cmd', '-w', 'wifi', 'set-wifi-enabled', on ? 'enabled' : 'disabled']); -}; +} /** * Change the state of Data transfer on the device under test. @@ -2028,7 +2026,7 @@ methods.setWifiState = async function setWifiState (on, isEmulator = false) { * @param {boolean} [isEmulator=false] - Set it to true if the device under test * is an emulator rather than a real device. */ -methods.setDataState = async function setDataState (on, isEmulator = false) { +export async function setDataState (on, isEmulator = false) { if (isEmulator) { // The svc command does not require to be root since API 26 await this.shell(['svc', 'data', on ? 'enable' : 'disable'], { @@ -2038,7 +2036,7 @@ methods.setDataState = async function setDataState (on, isEmulator = false) { } await this.shell(['cmd', 'phone', 'data', on ? 'enable' : 'disable']); -}; +} /** * Returns the list of TCP port states of the given family. @@ -2048,7 +2046,7 @@ methods.setDataState = async function setDataState (on, isEmulator = false) { * @param {PortFamily} [family='4'] * @returns {Promise} */ -methods.listPorts = async function listPorts(family = '4') { +export async function listPorts(family = '4') { const sourceProcName = `/proc/net/tcp${family === '6' ? '6' : ''}`; const output = await this.shell(['cat', sourceProcName]); const lines = output.split('\n'); @@ -2081,13 +2079,7 @@ methods.listPorts = async function listPorts(family = '4') { }); }; return result; -}; - -export default methods; - -/** - * @typedef {typeof methods} ADBCommands - */ +} /** * @typedef {'4'|'6'} PortFamily diff --git a/lib/tools/adb-emu-commands.js b/lib/tools/adb-emu-commands.js index 4978afff..72e2ff80 100644 --- a/lib/tools/adb-emu-commands.js +++ b/lib/tools/adb-emu-commands.js @@ -5,21 +5,19 @@ import net from 'net'; import { util, fs } from '@appium/support'; import B from 'bluebird'; import path from 'path'; -import ini from 'ini'; +import * as ini from 'ini'; -const emuMethods = {}; - -emuMethods.POWER_AC_STATES = Object.freeze({ +export const POWER_AC_STATES = Object.freeze({ POWER_AC_ON: 'on', POWER_AC_OFF: 'off' }); -emuMethods.GSM_CALL_ACTIONS = Object.freeze({ +export const GSM_CALL_ACTIONS = Object.freeze({ GSM_CALL: 'call', GSM_ACCEPT: 'accept', GSM_CANCEL: 'cancel', GSM_HOLD: 'hold' }); -emuMethods.GSM_VOICE_STATES = Object.freeze({ +export const GSM_VOICE_STATES = Object.freeze({ GSM_VOICE_UNREGISTERED: 'unregistered', GSM_VOICE_HOME: 'home', GSM_VOICE_ROAMING: 'roaming', @@ -29,9 +27,9 @@ emuMethods.GSM_VOICE_STATES = Object.freeze({ GSM_VOICE_ON: 'on' }); /** @typedef {0|1|2|3|4} GsmSignalStrength */ -emuMethods.GSM_SIGNAL_STRENGTHS = Object.freeze([0, 1, 2, 3, 4]); +export const GSM_SIGNAL_STRENGTHS = Object.freeze([0, 1, 2, 3, 4]); -emuMethods.NETWORK_SPEED = Object.freeze({ +export const NETWORK_SPEED = Object.freeze({ GSM: 'gsm', // GSM/CSD (up: 14.4, down: 14.4). SCSD: 'scsd', // HSCSD (up: 14.4, down: 57.6). GPRS: 'gprs', // GPRS (up: 28.8, down: 57.6). @@ -43,7 +41,7 @@ emuMethods.NETWORK_SPEED = Object.freeze({ FULL: 'full' // No limit, the default (up: 0.0, down: 0.0). }); -emuMethods.SENSORS = Object.freeze({ +export const SENSORS = Object.freeze({ ACCELERATION: 'acceleration', GYROSCOPE: 'gyroscope', MAGNETIC_FIELD: 'magnetic-field', @@ -63,11 +61,11 @@ emuMethods.SENSORS = Object.freeze({ }); /** - * @typedef {import('type-fest').ValueOf} Sensors - * @typedef {import('type-fest').ValueOf} NetworkSpeed - * @typedef {import('type-fest').ValueOf} GsmVoiceStates - * @typedef {import('type-fest').ValueOf} GsmCallActions - * @typedef {import('type-fest').ValueOf} PowerAcStates + * @typedef {import('type-fest').ValueOf} Sensors + * @typedef {import('type-fest').ValueOf} NetworkSpeed + * @typedef {import('type-fest').ValueOf} GsmVoiceStates + * @typedef {import('type-fest').ValueOf} GsmCallActions + * @typedef {import('type-fest').ValueOf} PowerAcStates * */ @@ -130,10 +128,10 @@ async function getAvdConfigPaths (avdsRoot) { * @this {import('../adb.js').ADB} * @return {Promise} True if Emulator is visible to adb. */ -emuMethods.isEmulatorConnected = async function isEmulatorConnected () { +export async function isEmulatorConnected () { let emulators = await this.getConnectedEmulators(); return !!_.find(emulators, (x) => x && x.udid === this.curDeviceId); -}; +} /** * Verify the emulator is connected. @@ -141,11 +139,11 @@ emuMethods.isEmulatorConnected = async function isEmulatorConnected () { * @this {import('../adb.js').ADB} * @throws {Error} If Emulator is not visible to adb. */ -emuMethods.verifyEmulatorConnected = async function verifyEmulatorConnected () { +export async function verifyEmulatorConnected () { if (!(await this.isEmulatorConnected())) { throw new Error(`The emulator "${this.curDeviceId}" was unexpectedly disconnected`); } -}; +} /** * Emulate fingerprint touch event on the connected emulator. @@ -153,7 +151,7 @@ emuMethods.verifyEmulatorConnected = async function verifyEmulatorConnected () { * @this {import('../adb.js').ADB} * @param {string} fingerprintId - The ID of the fingerprint. */ -emuMethods.fingerprint = async function fingerprint (fingerprintId) { +export async function fingerprint (fingerprintId) { if (!fingerprintId) { throw new Error('Fingerprint id parameter must be defined'); } @@ -163,7 +161,7 @@ emuMethods.fingerprint = async function fingerprint (fingerprintId) { throw new Error(`Device API Level must be >= 23. Current Api level '${level}'`); } await this.adbExecEmu(['finger', 'touch', fingerprintId]); -}; +} /** * Change the display orientation on the connected emulator. @@ -171,9 +169,9 @@ emuMethods.fingerprint = async function fingerprint (fingerprintId) { * this method is called. * @this {import('../adb.js').ADB} */ -emuMethods.rotate = async function rotate () { +export async function rotate () { await this.adbExecEmu(['rotate']); -}; +} /** * Emulate power state change on the connected emulator. @@ -181,13 +179,13 @@ emuMethods.rotate = async function rotate () { * @this {import('../adb.js').ADB} * @param {PowerAcStates} [state='on'] - Either 'on' or 'off'. */ -emuMethods.powerAC = async function powerAC (state = 'on') { - if (_.values(emuMethods.POWER_AC_STATES).indexOf(state) === -1) { +export async function powerAC (state = 'on') { + if (_.values(this.POWER_AC_STATES).indexOf(state) === -1) { throw new TypeError(`Wrong power AC state sent '${state}'. ` - + `Supported values: ${_.values(emuMethods.POWER_AC_STATES)}]`); + + `Supported values: ${_.values(this.POWER_AC_STATES)}]`); } await this.adbExecEmu(['power', 'ac', state]); -}; +} /** * Emulate sensors values on the connected emulator. @@ -197,10 +195,10 @@ emuMethods.powerAC = async function powerAC (state = 'on') { * @param {Sensors} value - Number to set as the sensor value. * @throws {TypeError} - If sensor type or sensor value is not defined */ -emuMethods.sensorSet = async function sensorSet (sensor, value) { - if (!_.includes(emuMethods.SENSORS, sensor)) { +export async function sensorSet (sensor, value) { + if (!_.includes(this.SENSORS, sensor)) { throw new TypeError(`Unsupported sensor sent '${sensor}'. ` - + `Supported values: ${_.values(emuMethods.SENSORS)}]`); + + `Supported values: ${_.values(this.SENSORS)}]`); } if (_.isNil(value)) { throw new TypeError(`Missing/invalid sensor value argument. ` @@ -208,7 +206,7 @@ emuMethods.sensorSet = async function sensorSet (sensor, value) { + `format [:[:[...]]].`); } await this.adbExecEmu(['sensor', 'set', sensor, `${value}`]); -}; +} /** * Emulate power capacity change on the connected emulator. @@ -216,22 +214,22 @@ emuMethods.sensorSet = async function sensorSet (sensor, value) { * @this {import('../adb.js').ADB} * @param {string|number} [percent=100] - Percentage value in range [0, 100]. */ -emuMethods.powerCapacity = async function powerCapacity (percent = 100) { +export async function powerCapacity (percent = 100) { percent = parseInt(`${percent}`, 10); if (isNaN(percent) || percent < 0 || percent > 100) { throw new TypeError(`The percentage value should be valid integer between 0 and 100`); } await this.adbExecEmu(['power', 'capacity', `${percent}`]); -}; +} /** * Emulate power off event on the connected emulator. * @this {import('../adb.js').ADB} */ -emuMethods.powerOFF = async function powerOFF () { - await this.powerAC(emuMethods.POWER_AC_STATES.POWER_AC_OFF); +export async function powerOFF () { + await this.powerAC(this.POWER_AC_STATES.POWER_AC_OFF); await this.powerCapacity(0); -}; +} /** * Emulate send SMS event on the connected emulator. @@ -241,7 +239,7 @@ emuMethods.powerOFF = async function powerOFF () { * @param {string} [message=''] - The message content. * @throws {TypeError} If phone number has invalid format. */ -emuMethods.sendSMS = async function sendSMS (phoneNumber, message = '') { +export async function sendSMS (phoneNumber, message = '') { if (_.isEmpty(message)) { throw new TypeError('SMS message must not be empty'); } @@ -249,7 +247,7 @@ emuMethods.sendSMS = async function sendSMS (phoneNumber, message = '') { throw new TypeError('Phone number most not be empty'); } await this.adbExecEmu(['sms', 'send', `${phoneNumber}`, message]); -}; +} /** * Emulate GSM call event on the connected emulator. @@ -260,17 +258,17 @@ emuMethods.sendSMS = async function sendSMS (phoneNumber, message = '') { * @throws {TypeError} If phone number has invalid format. * @throws {TypeError} If _action_ value is invalid. */ -emuMethods.gsmCall = async function gsmCall (phoneNumber, action) { - if (!_.values(emuMethods.GSM_CALL_ACTIONS).includes(action)) { +export async function gsmCall (phoneNumber, action) { + if (!_.values(this.GSM_CALL_ACTIONS).includes(action)) { throw new TypeError( - `Invalid gsm action param ${action}. Supported values: ${_.values(emuMethods.GSM_CALL_ACTIONS)}` + `Invalid gsm action param ${action}. Supported values: ${_.values(this.GSM_CALL_ACTIONS)}` ); } if (!_.isInteger(phoneNumber) && _.isEmpty(phoneNumber)) { throw new TypeError('Phone number most not be empty'); } await this.adbExecEmu(['gsm', action, `${phoneNumber}`]); -}; +} /** * Emulate GSM signal strength change event on the connected emulator. @@ -279,16 +277,16 @@ emuMethods.gsmCall = async function gsmCall (phoneNumber, action) { * @param {GsmSignalStrength} [strength=4] - A number in range [0, 4]; * @throws {TypeError} If _strength_ value is invalid. */ -emuMethods.gsmSignal = async function gsmSignal (strength = 4) { +export async function gsmSignal (strength = 4) { const strengthInt = parseInt(`${strength}`, 10); - if (!emuMethods.GSM_SIGNAL_STRENGTHS.includes(strengthInt)) { + if (!this.GSM_SIGNAL_STRENGTHS.includes(strengthInt)) { throw new TypeError( - `Invalid signal strength param ${strength}. Supported values: ${_.values(emuMethods.GSM_SIGNAL_STRENGTHS)}` + `Invalid signal strength param ${strength}. Supported values: ${_.values(this.GSM_SIGNAL_STRENGTHS)}` ); } log.info('gsm signal-profile changes the reported strength on next (15s) update.'); await this.adbExecEmu(['gsm', 'signal-profile', `${strength}`]); -}; +} /** * Emulate GSM voice event on the connected emulator. @@ -297,15 +295,15 @@ emuMethods.gsmSignal = async function gsmSignal (strength = 4) { * @param {GsmVoiceStates} [state='on'] - Either 'on' or 'off'. * @throws {TypeError} If _state_ value is invalid. */ -emuMethods.gsmVoice = async function gsmVoice (state = 'on') { +export async function gsmVoice (state = 'on') { // gsm voice allows you to change the state of your GPRS connection - if (!_.values(emuMethods.GSM_VOICE_STATES).includes(state)) { + if (!_.values(this.GSM_VOICE_STATES).includes(state)) { throw new TypeError( - `Invalid gsm voice state param ${state}. Supported values: ${_.values(emuMethods.GSM_VOICE_STATES)}` + `Invalid gsm voice state param ${state}. Supported values: ${_.values(this.GSM_VOICE_STATES)}` ); } await this.adbExecEmu(['gsm', 'voice', state]); -}; +} /** * Emulate network speed change event on the connected emulator. @@ -315,15 +313,15 @@ emuMethods.gsmVoice = async function gsmVoice (state = 'on') { * One of possible NETWORK_SPEED values. * @throws {TypeError} If _speed_ value is invalid. */ -emuMethods.networkSpeed = async function networkSpeed (speed = 'full') { +export async function networkSpeed (speed = 'full') { // network speed allows you to set the network speed emulation. - if (!_.values(emuMethods.NETWORK_SPEED).includes(speed)) { + if (!_.values(this.NETWORK_SPEED).includes(speed)) { throw new Error( - `Invalid network speed param ${speed}. Supported values: ${_.values(emuMethods.NETWORK_SPEED)}` + `Invalid network speed param ${speed}. Supported values: ${_.values(this.NETWORK_SPEED)}` ); } await this.adbExecEmu(['network', 'speed', speed]); -}; +} /** * @typedef {Object} ExecTelnetOptions @@ -349,7 +347,7 @@ emuMethods.networkSpeed = async function networkSpeed (speed = 'full') { * @throws {Error} If there was an error while connecting to the Telnet console * or if the given command returned non-OK response */ -emuMethods.execEmuConsoleCommand = async function execTelnet (cmd, opts = {}) { +export async function execEmuConsoleCommand (cmd, opts = {}) { let port = parseInt(`${opts.port}`, 10); if (!port) { const portMatch = /emulator-(\d+)/i.exec(/** @type {string} */(this.curDeviceId)); @@ -429,7 +427,7 @@ emuMethods.execEmuConsoleCommand = async function execTelnet (cmd, opts = {}) { } }); }); -}; +} /** * @typedef {Object} EmuVersionInfo @@ -444,7 +442,7 @@ emuMethods.execEmuConsoleCommand = async function execTelnet (cmd, opts = {}) { * @returns {Promise} If no version info could be parsed then an empty * object is returned */ -emuMethods.getEmuVersionInfo = async function getEmuVersionInfo () { +export async function getEmuVersionInfo () { const propsPath = path.join(/** @type {string} */ (this.sdkRoot), 'emulator', 'source.properties'); if (!await fs.exists(propsPath)) { return {}; @@ -461,7 +459,7 @@ emuMethods.getEmuVersionInfo = async function getEmuVersionInfo () { result.buildId = parseInt(buildIdMatch[1], 10); } return result; -}; +} /** * Retrieves emulator image properties from the local file system @@ -476,7 +474,7 @@ emuMethods.getEmuVersionInfo = async function getEmuVersionInfo () { * path.rel=avd/Pixel_XL_API_30.avd * target=android-30 */ -emuMethods.getEmuImageProperties = async function getEmuImageProperties (avdName) { +export async function getEmuImageProperties (avdName) { const avds = await listEmulators(); const avd = avds.find(({name}) => name === avdName); if (!avd) { @@ -489,7 +487,7 @@ emuMethods.getEmuImageProperties = async function getEmuImageProperties (avdName throw new Error(msg); } return ini.parse(await fs.readFile(avd.config, 'utf8')); -}; +} /** * Check if given emulator exists in the list of available avds. @@ -499,7 +497,7 @@ emuMethods.getEmuImageProperties = async function getEmuImageProperties (avdName * Should NOT start with '@' character * @throws {Error} If the emulator with given name does not exist. */ -emuMethods.checkAvdExist = async function checkAvdExist (avdName) { +export async function checkAvdExist (avdName) { const avds = await listEmulators(); if (!avds.some(({name}) => name === avdName)) { let msg = `Avd '${avdName}' is not available. `; @@ -511,11 +509,4 @@ emuMethods.checkAvdExist = async function checkAvdExist (avdName) { throw new Error(msg); } return true; -}; - - -export default emuMethods; - -/** - * @typedef {typeof emuMethods} ADBEmuCommands - */ +} diff --git a/lib/tools/android-manifest.js b/lib/tools/android-manifest.js index af22988e..5397d50c 100644 --- a/lib/tools/android-manifest.js +++ b/lib/tools/android-manifest.js @@ -7,8 +7,6 @@ import { import { fs, zip, tempDir, util } from '@appium/support'; import path from 'path'; -const manifestMethods = {}; - /** * @typedef {Object} APKInfo * @property {string} apkPackage - The name of application package, for example 'com.acme.app'. @@ -24,7 +22,7 @@ const manifestMethods = {}; * @throws {error} If there was an error while getting the data from the given * application package. */ -manifestMethods.packageAndLaunchActivityFromManifest = async function packageAndLaunchActivityFromManifest (appPath) { +export async function packageAndLaunchActivityFromManifest (appPath) { if (appPath.endsWith(APKS_EXTENSION)) { appPath = await this.extractBaseApk(appPath); } @@ -38,7 +36,7 @@ manifestMethods.packageAndLaunchActivityFromManifest = async function packageAnd log.info(`Package name: '${apkPackage}'`); log.info(`Main activity name: '${apkActivity}'`); return {apkPackage, apkActivity}; -}; +} /** * Extract target SDK version from application manifest. @@ -49,7 +47,7 @@ manifestMethods.packageAndLaunchActivityFromManifest = async function packageAnd * @throws {error} If there was an error while getting the data from the given * application package. */ -manifestMethods.targetSdkVersionFromManifest = async function targetSdkVersionFromManifest (appPath) { +export async function targetSdkVersionFromManifest (appPath) { log.debug(`Extracting target SDK version of '${appPath}'`); const originalAppPath = appPath; if (appPath.endsWith(APKS_EXTENSION)) { @@ -64,7 +62,7 @@ manifestMethods.targetSdkVersionFromManifest = async function targetSdkVersionFr ); } return targetSdkVersion; -}; +} /** * Extract target SDK version from package information. @@ -75,13 +73,13 @@ manifestMethods.targetSdkVersionFromManifest = async function targetSdkVersionFr * _dumpsys package_ command. It may speed up the method execution. * @return {Promise} The version of the target SDK. */ -manifestMethods.targetSdkVersionUsingPKG = async function targetSdkVersionUsingPKG (pkg, cmdOutput = null) { +export async function targetSdkVersionUsingPKG (pkg, cmdOutput = null) { const stdout = cmdOutput || await this.shell(['dumpsys', 'package', pkg]); const targetSdkVersionMatch = new RegExp(/targetSdk=([^\s\s]+)/g).exec(stdout); return targetSdkVersionMatch && targetSdkVersionMatch.length >= 2 ? parseInt(targetSdkVersionMatch[1], 10) : 0; -}; +} /** * Create binary representation of package manifest (usually AndroidManifest.xml). @@ -93,7 +91,7 @@ manifestMethods.targetSdkVersionUsingPKG = async function targetSdkVersionUsingP * @param {string} manifestPackage - The name of the manifest package * @param {string} targetPackage - The name of the destination package */ -manifestMethods.compileManifest = async function compileManifest (manifest, manifestPackage, targetPackage) { +export async function compileManifest (manifest, manifestPackage, targetPackage) { const {platform, platformPath} = await getAndroidPlatformAndPath(/** @type {string} */ (this.sdkRoot)); if (!platform || !platformPath) { throw new Error('Cannot compile the manifest. The required platform does not exist (API level >= 17)'); @@ -144,7 +142,7 @@ manifestMethods.compileManifest = async function compileManifest (manifest, mani } } log.debug(`Compiled the manifest at '${resultPath}'`); -}; +} /** * Replace/insert the specially precompiled manifest file into the @@ -160,7 +158,7 @@ manifestMethods.compileManifest = async function compileManifest (manifest, mani * @param {string} dstApk - Full path to the resulting package. * The file will be overridden if it already exists. */ -manifestMethods.insertManifest = async function insertManifest (manifest, srcApk, dstApk) { +export async function insertManifest (manifest, srcApk, dstApk) { log.debug(`Inserting manifest '${manifest}', src: '${srcApk}', dst: '${dstApk}'`); await zip.assertValidZip(srcApk); await unzipFile(`${manifest}.apk`); @@ -173,7 +171,7 @@ manifestMethods.insertManifest = async function insertManifest (manifest, srcApk await exec((/** @type {import('@appium/types').StringRecord} */ (this.binaries)).aapt, [ 'remove', dstApk, manifestName ]); - } catch (ign) {} + } catch {} await exec((/** @type {import('@appium/types').StringRecord} */ (this.binaries)).aapt, [ 'add', dstApk, manifestName ], {cwd: path.dirname(manifest)}); @@ -198,7 +196,7 @@ manifestMethods.insertManifest = async function insertManifest (manifest, srcApk } } log.debug(`Manifest insertion into '${dstApk}' is completed`); -}; +} /** * Check whether package manifest contains Internet permissions. @@ -207,7 +205,7 @@ manifestMethods.insertManifest = async function insertManifest (manifest, srcApk * @param {string} appPath - The full path to .apk(s) package. * @return {Promise} True if the manifest requires Internet access permission. */ -manifestMethods.hasInternetPermissionFromManifest = async function hasInternetPermissionFromManifest (appPath) { +export async function hasInternetPermissionFromManifest (appPath) { log.debug(`Checking if '${appPath}' requires internet access permission in the manifest`); if (appPath.endsWith(APKS_EXTENSION)) { appPath = await this.extractBaseApk(appPath); @@ -215,10 +213,4 @@ manifestMethods.hasInternetPermissionFromManifest = async function hasInternetPe const {usesPermissions} = await readPackageManifest.bind(this)(appPath); return usesPermissions.some((/** @type {string} */ name) => name === 'android.permission.INTERNET'); -}; - -export default manifestMethods; - -/** - * @typedef {typeof manifestMethods} ManifestMethods - */ +} diff --git a/lib/tools/apk-signing.js b/lib/tools/apk-signing.js index acfbe165..967b0fe1 100644 --- a/lib/tools/apk-signing.js +++ b/lib/tools/apk-signing.js @@ -27,9 +27,6 @@ const SIGNED_APPS_CACHE = new LRUCache({ max: 30, }); - -const apkSigningMethods = {}; - /** * Execute apksigner utility with given arguments. * @@ -39,7 +36,7 @@ const apkSigningMethods = {}; * @throws {Error} If apksigner binary is not present on the local file system * or the return code is not equal to zero. */ -apkSigningMethods.executeApksigner = async function executeApksigner (args) { +export async function executeApksigner (args) { const apkSignerJar = await getApksignerForOs(this); const fullCmd = [ await getJavaForOs(), '-Xmx1024M', '-Xss1m', @@ -67,7 +64,7 @@ apkSigningMethods.executeApksigner = async function executeApksigner (args) { log.debug(`apksigner ${name}: ${stream}`); } return stdout; -}; +} /** * (Re)sign the given apk file on the local file system with the default certificate. @@ -76,7 +73,7 @@ apkSigningMethods.executeApksigner = async function executeApksigner (args) { * @param {string} apk - The full path to the local apk file. * @throws {Error} If signing fails. */ -apkSigningMethods.signWithDefaultCert = async function signWithDefaultCert (apk) { +export async function signWithDefaultCert (apk) { log.debug(`Signing '${apk}' with default cert`); if (!(await fs.exists(apk))) { throw new Error(`${apk} file doesn't exist.`); @@ -94,7 +91,7 @@ apkSigningMethods.signWithDefaultCert = async function signWithDefaultCert (apk) throw new Error(`Could not sign '${apk}' with the default certificate. ` + `Original error: ${e.stderr || e.stdout || e.message}`); } -}; +} /** * (Re)sign the given apk file on the local file system with a custom certificate. @@ -103,7 +100,7 @@ apkSigningMethods.signWithDefaultCert = async function signWithDefaultCert (apk) * @param {string} apk - The full path to the local apk file. * @throws {Error} If signing fails. */ -apkSigningMethods.signWithCustomCert = async function signWithCustomCert (apk) { +export async function signWithCustomCert (apk) { log.debug(`Signing '${apk}' with custom cert`); if (!(await fs.exists(/** @type {string} */(this.keystorePath)))) { throw new Error(`Keystore: ${this.keystorePath} doesn't exist.`); @@ -148,7 +145,7 @@ apkSigningMethods.signWithCustomCert = async function signWithCustomCert (apk) { `Original error: ${e.stderr || e.message}`); } } -}; +} /** * (Re)sign the given apk file on the local file system with either @@ -159,7 +156,7 @@ apkSigningMethods.signWithCustomCert = async function signWithCustomCert (apk) { * @param {string} appPath - The full path to the local .apk(s) file. * @throws {Error} If signing fails. */ -apkSigningMethods.sign = async function sign (appPath) { +export async function sign (appPath) { if (appPath.endsWith(APKS_EXTENSION)) { let message = 'Signing of .apks-files is not supported. '; if (this.useKeystore) { @@ -182,7 +179,7 @@ apkSigningMethods.sign = async function sign (appPath) { } else { await this.signWithDefaultCert(appPath); } -}; +} /** * Perform zip-aligning to the given local apk file. @@ -193,18 +190,18 @@ apkSigningMethods.sign = async function sign (appPath) { * or false if the apk has been already aligned. * @throws {Error} If zip-align fails. */ -apkSigningMethods.zipAlignApk = async function zipAlignApk (apk) { +export async function zipAlignApk (apk) { await this.initZipAlign(); try { await exec((/** @type {import('@appium/types').StringRecord} */ (this.binaries)).zipalign, ['-c', '4', apk]); log.debug(`${apk}' is already zip-aligned. Doing nothing`); return false; - } catch (e) { + } catch { log.debug(`'${apk}' is not zip-aligned. Aligning`); } try { await fs.access(apk, _fs.constants.W_OK); - } catch (e) { + } catch { throw new Error(`The file at '${apk}' is not writeable. ` + `Please grant write permissions to this file or to its parent folder '${path.dirname(apk)}' ` + `for the Appium process, so it can zip-align the file`); @@ -224,7 +221,7 @@ apkSigningMethods.zipAlignApk = async function zipAlignApk (apk) { } throw new Error(`zipAlignApk failed. Original error: ${e.stderr || e.message}`); } -}; +} /** * @typedef {Object} CertCheckOptions @@ -242,7 +239,7 @@ apkSigningMethods.zipAlignApk = async function zipAlignApk (apk) { * @param {CertCheckOptions} [opts={}] - Certificate checking options * @return {Promise} True if given application is already signed. */ -apkSigningMethods.checkApkCert = async function checkApkCert (appPath, pkg, opts = {}) { +export async function checkApkCert (appPath, pkg, opts = {}) { log.debug(`Checking app cert for ${appPath}`); if (!await fs.exists(appPath)) { log.debug(`'${appPath}' does not exist`); @@ -320,7 +317,7 @@ apkSigningMethods.checkApkCert = async function checkApkCert (appPath, pkg, opts throw new Error(`Cannot verify the signature of '${appPath}'. ` + `Original error: ${errMsg}`); } -}; +} /** * @typedef {Object} KeystoreHash @@ -337,7 +334,7 @@ apkSigningMethods.checkApkCert = async function checkApkCert (appPath, pkg, opts * @return {Promise} * @throws {Error} If getting keystore hash fails. */ -apkSigningMethods.getKeystoreHash = async function getKeystoreHash () { +export async function getKeystoreHash () { log.debug(`Getting hash of the '${this.keystorePath}' keystore`); const keytool = path.resolve(await getJavaHome(), 'bin', `keytool${system.isWindows() ? '.exe' : ''}`); @@ -376,10 +373,4 @@ apkSigningMethods.getKeystoreHash = async function getKeystoreHash () { throw new Error(`Cannot get the hash of '${this.keystorePath}' keystore. ` + `Original error: ${e.stderr || e.message}`); } -}; - -export default apkSigningMethods; - -/** - * @typedef {typeof apkSigningMethods} ApkSigningCommands - */ +} diff --git a/lib/tools/apk-utils.js b/lib/tools/apk-utils.js index f4728618..b6c0ddb1 100644 --- a/lib/tools/apk-utils.js +++ b/lib/tools/apk-utils.js @@ -10,22 +10,20 @@ import path from 'path'; import _ from 'lodash'; import { retryInterval, waitForCondition } from 'asyncbox'; import { fs, util, mkdirp, timing } from '@appium/support'; -import semver from 'semver'; +import * as semver from 'semver'; import os from 'os'; import { LRUCache } from 'lru-cache'; -const apkUtilsMethods = {}; - /** @typedef {'unknown'|'notInstalled'|'newerVersionInstalled'|'sameVersionInstalled'|'olderVersionInstalled'} InstallState */ /** @type {Record} */ -apkUtilsMethods.APP_INSTALL_STATE = { +export const APP_INSTALL_STATE = { UNKNOWN: 'unknown', NOT_INSTALLED: 'notInstalled', NEWER_VERSION_INSTALLED: 'newerVersionInstalled', SAME_VERSION_INSTALLED: 'sameVersionInstalled', OLDER_VERSION_INSTALLED: 'olderVersionInstalled', }; -const REMOTE_CACHE_ROOT = '/data/local/tmp/appium_cache'; +export const REMOTE_CACHE_ROOT = '/data/local/tmp/appium_cache'; const RESOLVER_ACTIVITY_NAME = 'android/com.android.internal.app.ResolverActivity'; @@ -42,7 +40,7 @@ const RESOLVER_ACTIVITY_NAME = 'android/com.android.internal.app.ResolverActivit * @param {IsAppInstalledOptions} [opts={}] * @return {Promise} True if the package is installed. */ -apkUtilsMethods.isAppInstalled = async function isAppInstalled (pkg, opts = {}) { +export async function isAppInstalled (pkg, opts = {}) { const { user, } = opts; @@ -59,7 +57,7 @@ apkUtilsMethods.isAppInstalled = async function isAppInstalled (pkg, opts = {}) cmd.push(pkg); const stdout = await this.shell(cmd); isInstalled = /^package:/m.test(stdout); - } catch (ign) { + } catch { isInstalled = false; } } else { @@ -83,7 +81,7 @@ apkUtilsMethods.isAppInstalled = async function isAppInstalled (pkg, opts = {}) } log.debug(`'${pkg}' is${!isInstalled ? ' not' : ''} installed`); return isInstalled; -}; +} /** * @typedef {Object} StartUriOptions @@ -99,7 +97,7 @@ apkUtilsMethods.isAppInstalled = async function isAppInstalled (pkg, opts = {}) * @param {string?} [pkg=null] - The name of the package to start the URI with. * @param {StartUriOptions} [opts={}] */ -apkUtilsMethods.startUri = async function startUri (uri, pkg = null, opts = {}) { +export async function startUri (uri, pkg = null, opts = {}) { const { waitForLaunch = true, } = opts; @@ -126,7 +124,7 @@ apkUtilsMethods.startUri = async function startUri (uri, pkg = null, opts = {}) } catch (e) { throw new Error(`Error attempting to start URI. Original error: ${e}`); } -}; +} /** * @typedef {Object} StartAppOptions @@ -165,7 +163,7 @@ apkUtilsMethods.startUri = async function startUri (uri, pkg = null, opts = {}) * @return {Promise} The output of the corresponding adb command. * @throws {Error} If there is an error while executing the activity */ -apkUtilsMethods.startApp = async function startApp (startAppOptions) { +export async function startApp (startAppOptions) { if (!startAppOptions.pkg || !(startAppOptions.activity || startAppOptions.action)) { throw new Error('pkg, and activity or intent action, are required to start an application'); } @@ -227,14 +225,14 @@ apkUtilsMethods.startApp = async function startApp (startAppOptions) { `Consider checking the driver's troubleshooting documentation. ` + `Original error: ${e.message}`); } -}; +} /** * Helper method to call `adb dumpsys window windows/displays` * @this {import('../adb.js').ADB} * @returns {Promise} */ -apkUtilsMethods.dumpWindows = async function dumpWindows () { +export async function dumpWindows () { const apiLevel = await this.getApiLevel(); // With version 29, Android changed the dumpsys syntax @@ -242,7 +240,7 @@ apkUtilsMethods.dumpWindows = async function dumpWindows () { const cmd = ['dumpsys', 'window', dumpsysArg]; return await this.shell(cmd); -}; +} /** * @typedef {Object} PackageActivityInfo @@ -258,7 +256,7 @@ apkUtilsMethods.dumpWindows = async function dumpWindows () { * @return {Promise} The mapping, where property names are 'appPackage' and 'appActivity'. * @throws {Error} If there is an error while parsing the data. */ -apkUtilsMethods.getFocusedPackageAndActivity = async function getFocusedPackageAndActivity () { +export async function getFocusedPackageAndActivity () { log.debug('Getting focused package and activity'); let stdout; try { @@ -316,7 +314,7 @@ apkUtilsMethods.getFocusedPackageAndActivity = async function getFocusedPackageA log.debug(stdout); throw new Error('Could not retrieve the currently focused package and activity'); -}; +} /** * Wait for the given activity to be focused/non-focused. @@ -330,7 +328,7 @@ apkUtilsMethods.getFocusedPackageAndActivity = async function getFocusedPackageA * @param {number} [waitMs=20000] - Number of milliseconds to wait before timeout occurs. * @throws {error} If timeout happens. */ -apkUtilsMethods.waitForActivityOrNot = async function waitForActivityOrNot (pkg, activity, waitForStop, waitMs = 20000) { +export async function waitForActivityOrNot (pkg, activity, waitForStop, waitMs = 20000) { if (!pkg || !activity) { throw new Error('Package and activity required.'); } @@ -387,11 +385,11 @@ apkUtilsMethods.waitForActivityOrNot = async function waitForActivityOrNot (pkg, waitMs: parseInt(`${waitMs}`, 10), intervalMs: 500, }); - } catch (e) { + } catch { throw new Error(`${possibleActivityNames.map((name) => `'${name}'`).join(' or ')} never ${waitForStop ? 'stopped' : 'started'}. ` + `Consider checking the driver's troubleshooting documentation.`); } -}; +} /** * Wait for the given activity to be focused @@ -403,9 +401,9 @@ apkUtilsMethods.waitForActivityOrNot = async function waitForActivityOrNot (pkg, * @param {number} [waitMs=20000] - Number of milliseconds to wait before timeout occurs. * @throws {error} If timeout happens. */ -apkUtilsMethods.waitForActivity = async function waitForActivity (pkg, act, waitMs = 20000) { +export async function waitForActivity (pkg, act, waitMs = 20000) { await this.waitForActivityOrNot(pkg, act, false, waitMs); -}; +} /** * Wait for the given activity to be non-focused. @@ -417,9 +415,9 @@ apkUtilsMethods.waitForActivity = async function waitForActivity (pkg, act, wait * @param {number} [waitMs=20000] - Number of milliseconds to wait before timeout occurs. * @throws {error} If timeout happens. */ -apkUtilsMethods.waitForNotActivity = async function waitForNotActivity (pkg, act, waitMs = 20000) { +export async function waitForNotActivity (pkg, act, waitMs = 20000) { await this.waitForActivityOrNot(pkg, act, true, waitMs); -}; +} /** * @typedef {Object} UninstallOptions @@ -440,7 +438,7 @@ apkUtilsMethods.waitForNotActivity = async function waitForNotActivity (pkg, act * @return {Promise} True if the package was found on the device and * successfully uninstalled. */ -apkUtilsMethods.uninstallApk = async function uninstallApk (pkg, options = {}) { +export async function uninstallApk (pkg, options = {}) { log.debug(`Uninstalling ${pkg}`); if (!options.skipInstallCheck && !await this.isAppInstalled(pkg)) { log.info(`${pkg} was not uninstalled, because it was not present on the device`); @@ -467,7 +465,7 @@ apkUtilsMethods.uninstallApk = async function uninstallApk (pkg, options = {}) { } log.info(`${pkg} was not uninstalled`); return false; -}; +} /** * Install the package after it was pushed to the device under test. @@ -477,12 +475,12 @@ apkUtilsMethods.uninstallApk = async function uninstallApk (pkg, options = {}) { * @param {import('./system-calls.js').ShellExecOptions} [opts={}] Additional exec options. * @throws {error} If there was a failure during application install. */ -apkUtilsMethods.installFromDevicePath = async function installFromDevicePath (apkPathOnDevice, opts = {}) { +export async function installFromDevicePath (apkPathOnDevice, opts = {}) { const stdout = /** @type {string} */ (await this.shell(['pm', 'install', '-r', apkPathOnDevice], opts)); if (stdout.includes('Failure')) { throw new Error(`Remote install failed: ${stdout}`); } -}; +} /** * @typedef {Object} CachingOptions @@ -499,7 +497,7 @@ apkUtilsMethods.installFromDevicePath = async function installFromDevicePath (ap * @returns {Promise} - Full path to the cached apk on the remote file system * @throws {Error} if there was a failure while caching the app */ -apkUtilsMethods.cacheApk = async function cacheApk (apkPath, options = {}) { +export async function cacheApk (apkPath, options = {}) { const appHash = await fs.hash(apkPath); const remotePath = path.posix.join(REMOTE_CACHE_ROOT, `${appHash}.apk`); const remoteCachedFiles = []; @@ -575,7 +573,7 @@ apkUtilsMethods.cacheApk = async function cacheApk (apkPath, options = {}) { } } return remotePath; -}; +} /** * @typedef {Object} InstallOptions @@ -607,7 +605,7 @@ apkUtilsMethods.cacheApk = async function cacheApk (apkPath, options = {}) { * @param {InstallOptions} [options={}] - The set of installation options. * @throws {Error} If an unexpected error happens during install. */ -apkUtilsMethods.install = async function install (appPath, options = {}) { +export async function install (appPath, options = {}) { if (appPath.endsWith(APKS_EXTENSION)) { return await this.installApks(appPath, options); } @@ -703,7 +701,7 @@ apkUtilsMethods.install = async function install (appPath, options = {}) { } log.debug(`Application '${appPath}' already installed. Continuing.`); } -}; +} /** * Retrieves the current installation state of the particular application @@ -714,7 +712,7 @@ apkUtilsMethods.install = async function install (appPath, options = {}) { * try to extract it on its own * @returns {Promise} One of `APP_INSTALL_STATE` constants */ -apkUtilsMethods.getApplicationInstallState = async function getApplicationInstallState (appPath, pkg = null) { +export async function getApplicationInstallState (appPath, pkg = null) { let apkInfo = null; if (!pkg) { apkInfo = await this.getApkInfo(appPath); @@ -777,7 +775,7 @@ apkUtilsMethods.getApplicationInstallState = async function getApplicationInstal log.debug(`The installed '${pkg}' package is older than '${appPath}' (${pkgVersionCode} < ${apkVersionCode} or '${pkgVersionName}' < '${apkVersionName}')'`); return this.APP_INSTALL_STATE.OLDER_VERSION_INSTALLED; -}; +} /** * @typedef {Object} InstallOrUpgradeOptions @@ -816,7 +814,7 @@ apkUtilsMethods.getApplicationInstallState = async function getApplicationInstal * @throws {Error} If an unexpected error happens during install. * @returns {Promise} */ -apkUtilsMethods.installOrUpgrade = async function installOrUpgrade (appPath, pkg = null, options = {}) { +export async function installOrUpgrade (appPath, pkg = null, options = {}) { if (!pkg) { const apkInfo = await this.getApkInfo(appPath); if ('name' in apkInfo) { @@ -887,7 +885,7 @@ apkUtilsMethods.installOrUpgrade = async function installOrUpgrade (appPath, pkg appState, wasUninstalled, }; -}; +} /** * @typedef {Object} ApkStrings @@ -908,7 +906,7 @@ apkUtilsMethods.installOrUpgrade = async function installOrUpgrade (appPath, pkg * will be undefined. * @return {Promise} */ -apkUtilsMethods.extractStringsFromApk = async function extractStringsFromApk ( +export async function extractStringsFromApk ( appPath, language = null, outRoot = null @@ -975,7 +973,7 @@ apkUtilsMethods.extractStringsFromApk = async function extractStringsFromApk ( await mkdirp(outRoot); await fs.writeFile(localPath, JSON.stringify(apkStrings, null, 2), 'utf-8'); return {apkStrings, localPath}; -}; +} /** * Get the language name of the device under test. @@ -983,11 +981,11 @@ apkUtilsMethods.extractStringsFromApk = async function extractStringsFromApk ( * @this {import('../adb.js').ADB} * @return {Promise} The name of device language. */ -apkUtilsMethods.getDeviceLanguage = async function getDeviceLanguage () { +export async function getDeviceLanguage () { return await this.getApiLevel() < 23 ? (await this.getDeviceSysLanguage() || await this.getDeviceProductLanguage()) : (await this.getDeviceLocale()).split('-')[0]; -}; +} /** * Get the country name of the device under test. @@ -996,9 +994,9 @@ apkUtilsMethods.getDeviceLanguage = async function getDeviceLanguage () { * @this {import('../adb.js').ADB} * @return {Promise} The name of device country. */ -apkUtilsMethods.getDeviceCountry = async function getDeviceCountry () { +export async function getDeviceCountry () { return await this.getDeviceSysCountry() || await this.getDeviceProductCountry(); -}; +} /** * Get the locale name of the device under test. @@ -1007,9 +1005,9 @@ apkUtilsMethods.getDeviceCountry = async function getDeviceCountry () { * @this {import('../adb.js').ADB} * @return {Promise} The name of device locale. */ -apkUtilsMethods.getDeviceLocale = async function getDeviceLocale () { +export async function getDeviceLocale () { return await this.getDeviceSysLocale() || await this.getDeviceProductLocale(); -}; +} /** * Make sure current device locale is expected or not. @@ -1022,7 +1020,7 @@ apkUtilsMethods.getDeviceLocale = async function getDeviceLocale () { * * @return {Promise} If current locale is language and country as arguments, return true. */ -apkUtilsMethods.ensureCurrentLocale = async function ensureCurrentLocale (language, country, script) { +export async function ensureCurrentLocale (language, country, script) { const hasLanguage = _.isString(language); const hasCountry = _.isString(country); if (!hasLanguage && !hasCountry) { @@ -1071,7 +1069,7 @@ apkUtilsMethods.ensureCurrentLocale = async function ensureCurrentLocale (langua } return [languagePattern, countryPattern].every(checkLocalePattern); })); -}; +} /** * @typedef {Object} AppInfo @@ -1089,7 +1087,7 @@ apkUtilsMethods.ensureCurrentLocale = async function ensureCurrentLocale (langua * file system. * @return {Promise} The parsed application information. */ -apkUtilsMethods.getApkInfo = async function getApkInfo (appPath) { +export async function getApkInfo (appPath) { if (!await fs.exists(appPath)) { throw new Error(`The file at path ${appPath} does not exist or is not accessible`); } @@ -1109,7 +1107,7 @@ apkUtilsMethods.getApkInfo = async function getApkInfo (appPath) { log.warn(`Error '${e.message}' while getting badging info`); } return {}; -}; +} /** * Get the package info from the installed application. @@ -1118,7 +1116,7 @@ apkUtilsMethods.getApkInfo = async function getApkInfo (appPath) { * @param {string} pkg - The name of the installed package. * @return {Promise} The parsed application information. */ -apkUtilsMethods.getPackageInfo = async function getPackageInfo (pkg) { +export async function getPackageInfo (pkg) { log.debug(`Getting package info for '${pkg}'`); const result = {name: pkg}; let stdout; @@ -1145,7 +1143,7 @@ apkUtilsMethods.getPackageInfo = async function getPackageInfo (pkg) { result.versionCode = parseInt(versionCodeMatch[1], 10); } return result; -}; +} /** * Fetches base.apk of the given package to the local file system @@ -1156,7 +1154,7 @@ apkUtilsMethods.getPackageInfo = async function getPackageInfo (pkg) { * @returns {Promise} Full path to the downloaded file * @throws {Error} If there was an error while fetching the .apk */ -apkUtilsMethods.pullApk = async function pullApk (pkg, tmpDir) { +export async function pullApk (pkg, tmpDir) { const stdout = _.trim(await this.shell(['pm', 'path', pkg])); const packageMarker = 'package:'; if (!_.startsWith(stdout, packageMarker)) { @@ -1168,7 +1166,7 @@ apkUtilsMethods.pullApk = async function pullApk (pkg, tmpDir) { await this.pull(remotePath, tmpApp); log.debug(`Pulled app for package '${pkg}' to '${tmpApp}'`); return tmpApp; -}; +} /** * Activates the given application or launches it if necessary. @@ -1179,7 +1177,7 @@ apkUtilsMethods.pullApk = async function pullApk (pkg, tmpDir) { * @param {string} appId - Application package identifier * @throws {Error} If the app cannot be activated */ -apkUtilsMethods.activateApp = async function activateApp (appId) { +export async function activateApp (appId) { log.debug(`Activating '${appId}'`); const apiLevel = await this.getApiLevel(); // Fallback to Monkey in older APIs @@ -1227,11 +1225,4 @@ apkUtilsMethods.activateApp = async function activateApp (appId) { if (/^error:/mi.test(stdout)) { throw new Error(`Cannot activate '${appId}'. Original error: ${stdout}`); } -}; - -export { REMOTE_CACHE_ROOT }; -export default apkUtilsMethods; - -/** - * @typedef {typeof apkUtilsMethods} ApkUtils - */ +} diff --git a/lib/tools/apks-utils.js b/lib/tools/apks-utils.js index bf94c593..109b1f57 100644 --- a/lib/tools/apks-utils.js +++ b/lib/tools/apks-utils.js @@ -20,8 +20,6 @@ const APKS_CACHE = new LRUCache({ const APKS_CACHE_GUARD = new AsyncLock(); const BUNDLETOOL_TIMEOUT_MS = 4 * 60 * 1000; -const apksUtilsMethods = {}; - process.on('exit', () => { if (!APKS_CACHE.size) { return; @@ -98,7 +96,7 @@ async function extractFromApks (apks, dstPath) { * @throws {Error} If bundletool jar does not exist in PATH or there was an error while * executing it */ -apksUtilsMethods.execBundletool = async function execBundletool (args, errorMsg) { +export async function execBundletool (args, errorMsg) { await this.initBundletool(); args = [ '-jar', (/** @type {import('@appium/types').StringRecord} */ (this.binaries)).bundletool, @@ -129,7 +127,7 @@ apksUtilsMethods.execBundletool = async function execBundletool (args, errorMsg) } throw new Error(`${errorMsg}. Original error: ${e.message}`); } -}; +} /** * @@ -138,7 +136,7 @@ apksUtilsMethods.execBundletool = async function execBundletool (args, errorMsg) * @returns {Promise} The same `specLocation` value * @throws {Error} If it is not possible to retrieve the spec for the current device */ -apksUtilsMethods.getDeviceSpec = async function getDeviceSpec (specLocation) { +export async function getDeviceSpec (specLocation) { /** @type {string[]} */ const args = [ 'get-device-spec', @@ -149,7 +147,7 @@ apksUtilsMethods.getDeviceSpec = async function getDeviceSpec (specLocation) { log.debug(`Getting the spec for the device '${this.curDeviceId}'`); await this.execBundletool(args, 'Cannot retrieve the device spec'); return specLocation; -}; +} /** * @typedef {Object} InstallMultipleApksOptions @@ -175,14 +173,14 @@ apksUtilsMethods.getDeviceSpec = async function getDeviceSpec (specLocation) { * @param {Array} apkPathsToInstall - The full paths to install apks * @param {InstallMultipleApksOptions} [options={}] - Installation options */ -apksUtilsMethods.installMultipleApks = async function installMultipleApks (apkPathsToInstall, options = {}) { +export async function installMultipleApks (apkPathsToInstall, options = {}) { const installArgs = buildInstallArgs(await this.getApiLevel(), options); return await this.adbExec(['install-multiple', ...installArgs, ...apkPathsToInstall], { // @ts-ignore This validation works timeout: isNaN(options.timeout) ? undefined : options.timeout, timeoutCapName: options.timeoutCapName, }); -}; +} /** * @typedef {Object} InstallApksOptions @@ -205,7 +203,7 @@ apksUtilsMethods.installMultipleApks = async function installMultipleApks (apkPa * @param {InstallApksOptions} [options={}] - Installation options * @throws {Error} If the .apks bundle cannot be installed */ -apksUtilsMethods.installApks = async function installApks (apks, options = {}) { +export async function installApks (apks, options = {}) { const { grantPermissions, allowTestPackages, @@ -235,7 +233,7 @@ apksUtilsMethods.installApks = async function installApks (apks, options = {}) { // 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. @@ -245,9 +243,9 @@ apksUtilsMethods.installApks = async function installApks (apks, options = {}) { * @returns {Promise} The full path to the master bundle .apk * @throws {Error} If there was an error while extracting/finding the file */ -apksUtilsMethods.extractBaseApk = async function extractBaseApk (apks) { +export async function extractBaseApk (apks) { return await extractFromApks(apks, ['splits', BASE_APK]); -}; +} /** * Extracts and returns the full path to the .apk, which contains the corresponding @@ -261,7 +259,7 @@ apksUtilsMethods.extractBaseApk = async function extractBaseApk (apks) { * if language split is not enabled for the bundle. * @throws {Error} If there was an error while extracting/finding the file */ -apksUtilsMethods.extractLanguageApk = async function extractLanguageApk (apks, language = null) { +export async function extractLanguageApk (apks, language = null) { if (language) { try { return await extractFromApks(apks, ['splits', LANGUAGE_APK(language)]); @@ -277,26 +275,19 @@ apksUtilsMethods.extractLanguageApk = async function extractLanguageApk (apks, l for (const lang of defaultLanguages) { try { return await extractFromApks(apks, ['splits', LANGUAGE_APK(lang)]); - } catch (ign) {} + } catch {} } 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 {string} output * @returns {boolean} */ -apksUtilsMethods.isTestPackageOnlyError = function isTestPackageOnlyError (output) { +export function isTestPackageOnlyError (output) { return /\[INSTALL_FAILED_TEST_ONLY\]/.test(output); -}; - -export default apksUtilsMethods; - - -/** - * @typedef {typeof apksUtilsMethods} ApksUtils - */ +} diff --git a/lib/tools/index.ts b/lib/tools/index.ts deleted file mode 100644 index 09ca9956..00000000 --- a/lib/tools/index.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @privateRemarks This is a `.ts` file so we can re-export types from other - * files; otherwise we would need to copy `@typedef`s around. - * @module - */ - -import methods from './adb-commands'; -import manifestMethods from './android-manifest'; -import systemCallMethods, {getAndroidBinaryPath} from './system-calls'; -import apkSigningMethods from './apk-signing'; -import apkUtilsMethods from './apk-utils'; -import apksUtilsMethods from './apks-utils'; -import aabUtilsMethods from './aab-utils'; -import emuMethods from './adb-emu-commands'; -import lockManagementCommands from './lockmgmt'; -import keyboardCommands from './keyboard-commands'; - -Object.assign( - methods, - manifestMethods, - systemCallMethods, - emuMethods, - apkSigningMethods, - apkUtilsMethods, - apksUtilsMethods, - aabUtilsMethods, - lockManagementCommands, - keyboardCommands -); - -export default methods; -export {getAndroidBinaryPath}; - -export type * from './adb-commands'; -export type * from './system-calls'; -export type * from './adb-emu-commands'; -export type * from './apk-signing'; -export type * from './apk-utils'; -export type * from './apks-utils'; -export type * from './aab-utils'; -export type * from './android-manifest'; -export type * from './keyboard-commands'; -export type * from './lockmgmt'; diff --git a/lib/tools/keyboard-commands.js b/lib/tools/keyboard-commands.js index ab165adc..581fe720 100644 --- a/lib/tools/keyboard-commands.js +++ b/lib/tools/keyboard-commands.js @@ -4,8 +4,6 @@ import { waitForCondition } from 'asyncbox'; const KEYCODE_ESC = 111; const KEYCODE_BACK = 4; -const keyboardCommands = {}; - /** * Hides software keyboard if it is visible. * Noop if the keyboard is already hidden. @@ -16,7 +14,7 @@ const keyboardCommands = {}; * @returns {Promise} `false` if the keyboard was already hidden * @throws {Error} If the keyboard cannot be hidden. */ -keyboardCommands.hideKeyboard = async function hideKeyboard (timeoutMs = 1000) { +export async function hideKeyboard (timeoutMs = 1000) { let {isKeyboardShown, canCloseKeyboard} = await this.isSoftKeyboardPresent(); if (!isKeyboardShown) { log.info('Keyboard has no UI; no closing necessary'); @@ -32,7 +30,7 @@ keyboardCommands.hideKeyboard = async function hideKeyboard (timeoutMs = 1000) { ({isKeyboardShown} = await this.isSoftKeyboardPresent()); return !isKeyboardShown; }, {waitMs: timeoutMs, intervalMs: 500}); - } catch (ign) {} + } catch {} } throw new Error(`The software keyboard cannot be hidden`); }; @@ -49,7 +47,7 @@ keyboardCommands.hideKeyboard = async function hideKeyboard (timeoutMs = 1000) { * @this {import('../adb.js').ADB} * @return {Promise} The keyboard state. */ -keyboardCommands.isSoftKeyboardPresent = async function isSoftKeyboardPresent () { +export async function isSoftKeyboardPresent () { try { const stdout = await this.shell(['dumpsys', 'input_method']); const inputShownMatch = /mInputShown=(\w+)/.exec(stdout); @@ -62,9 +60,3 @@ keyboardCommands.isSoftKeyboardPresent = async function isSoftKeyboardPresent () throw new Error(`Error finding softkeyboard. Original error: ${e.message}`); } }; - -export default keyboardCommands; - -/** - * @typedef {typeof keyboardCommands} KeyboardCommands - */ diff --git a/lib/tools/lockmgmt.js b/lib/tools/lockmgmt.js index 9253fca8..1db85b16 100644 --- a/lib/tools/lockmgmt.js +++ b/lib/tools/lockmgmt.js @@ -6,8 +6,6 @@ import { } from '../helpers.js'; import B from 'bluebird'; -const lockManagementMethods = {}; - const CREDENTIAL_CANNOT_BE_NULL_OR_EMPTY_ERROR = `Credential can't be null or empty`; const CREDENTIAL_DID_NOT_MATCH_ERROR = `didn't match`; const SUPPORTED_LOCK_CREDENTIAL_TYPES = ['password', 'pin', 'pattern']; @@ -62,19 +60,19 @@ async function swipeUp (windowDumpsys) { * @this {import('../adb.js').ADB} * @return {Promise} True if the management is supported. The result is cached per ADB instance */ -lockManagementMethods.isLockManagementSupported = async function isLockManagementSupported () { +export async function isLockManagementSupported () { if (!_.isBoolean(this._isLockManagementSupported)) { const passFlag = '__PASS__'; let output = ''; try { output = await this.shell([`locksettings help && echo ${passFlag}`]); - } catch (ign) {} + } catch {} this._isLockManagementSupported = _.includes(output, passFlag); log.debug(`Extended lock settings management is ` + `${this._isLockManagementSupported ? '' : 'not '}supported`); } return this._isLockManagementSupported; -}; +} /** * Check whether the given credential is matches to the currently set one. @@ -89,7 +87,7 @@ lockManagementMethods.isLockManagementSupported = async function isLockManagemen * @return {Promise} True if the given credential matches to the device's one * @throws {Error} If the verification faces an unexpected error */ -lockManagementMethods.verifyLockCredential = async function verifyLockCredential (credential = null) { +export async function verifyLockCredential (credential = null) { try { const {stdout, stderr} = await this.shell(buildCommand('verify', credential), { outputFormat: this.EXEC_OUTPUT_FORMAT.FULL @@ -106,7 +104,7 @@ lockManagementMethods.verifyLockCredential = async function verifyLockCredential throw new Error(`Device lock credential verification failed. ` + `Original error: ${e.stderr || e.stdout || e.message}`); } -}; +} /** * Clears current lock credentials. Usually it takes several seconds for a device to @@ -121,7 +119,7 @@ lockManagementMethods.verifyLockCredential = async function verifyLockCredential * null/empty value assumes the device has no lock currently set. * @throws {Error} If operation faces an unexpected error */ -lockManagementMethods.clearLockCredential = async function clearLockCredential (credential = null) { +export async function clearLockCredential (credential = null) { try { const {stdout, stderr} = await this.shell(buildCommand('clear', credential), { outputFormat: this.EXEC_OUTPUT_FORMAT.FULL @@ -134,7 +132,7 @@ lockManagementMethods.clearLockCredential = async function clearLockCredential ( throw new Error(`Cannot clear device lock credential. ` + `Original error: ${e.stderr || e.stdout || e.message}`); } -}; +} /** * Checks whether the device is locked with a credential (either pin or a password @@ -144,7 +142,7 @@ lockManagementMethods.clearLockCredential = async function clearLockCredential ( * @returns {Promise} `true` if the device is locked * @throws {Error} If operation faces an unexpected error */ -lockManagementMethods.isLockEnabled = async function isLockEnabled () { +export async function isLockEnabled () { try { const {stdout, stderr} = await this.shell(buildCommand('get-disabled'), { outputFormat: this.EXEC_OUTPUT_FORMAT.FULL @@ -161,7 +159,7 @@ lockManagementMethods.isLockEnabled = async function isLockEnabled () { } catch (e) { throw new Error(`Cannot check if device lock is enabled. Original error: ${e.message}`); } -}; +} /** * Sets the device lock. @@ -181,7 +179,7 @@ lockManagementMethods.isLockEnabled = async function isLockEnabled () { * @throws {Error} If there was a failure while verifying input arguments or setting * the credential */ -lockManagementMethods.setLockCredential = async function setLockCredential ( +export async function setLockCredential ( credentialType, credential, oldCredential = null) { if (!SUPPORTED_LOCK_CREDENTIAL_TYPES.includes(credentialType)) { throw new Error(`Device lock credential type '${credentialType}' is unknown. ` + @@ -202,7 +200,7 @@ lockManagementMethods.setLockCredential = async function setLockCredential ( throw new Error(`Setting of device lock ${credentialType} credential failed. ` + `Original error: ${e.stderr || e.stdout || e.message}`); } -}; +} /** * Retrieve the screen lock state of the device under test. @@ -210,7 +208,7 @@ lockManagementMethods.setLockCredential = async function setLockCredential ( * @this {import('../adb.js').ADB} * @return {Promise} True if the device is locked. */ -lockManagementMethods.isScreenLocked = async function isScreenLocked () { +export async function isScreenLocked () { const [windowOutput, powerOutput] = await B.all([ this.shell(['dumpsys', 'window']), this.shell(['dumpsys', 'power']), @@ -219,13 +217,13 @@ lockManagementMethods.isScreenLocked = async function isScreenLocked () { || isCurrentFocusOnKeyguard(windowOutput) || !isScreenOnFully(windowOutput) || isInDozingMode(powerOutput); -}; +} /** * Dismisses keyguard overlay. * @this {import('../adb.js').ADB} */ -lockManagementMethods.dismissKeyguard = async function dismissKeyguard () { +export async function dismissKeyguard () { log.info('Waking up the device to dismiss the keyguard'); // Screen off once to force pre-inputted text field clean after wake-up // Just screen on if the screen defaults off @@ -250,20 +248,14 @@ lockManagementMethods.dismissKeyguard = async function dismissKeyguard () { await this.shell(['service', 'call', 'notification', '1']); await this.back(); await swipeUp.bind(this)(stdout); -}; +} /** * Presses the corresponding key combination to make sure the device's screen * is not turned off and is locked if the latter is enabled. * @this {import('../adb.js').ADB} */ -lockManagementMethods.cycleWakeUp = async function cycleWakeUp () { +export async function cycleWakeUp () { await this.keyevent(KEYCODE_POWER); await this.keyevent(KEYCODE_WAKEUP); -}; - -export default lockManagementMethods; - -/** - * @typedef {typeof lockManagementMethods} LockManagementCommands - */ +} diff --git a/lib/tools/system-calls.js b/lib/tools/system-calls.js index 26c363e8..a1f76e7a 100644 --- a/lib/tools/system-calls.js +++ b/lib/tools/system-calls.js @@ -9,11 +9,9 @@ import { import { exec, SubProcess } from 'teen_process'; import { sleep, retry, retryInterval, waitForCondition } from 'asyncbox'; import _ from 'lodash'; -import semver from 'semver'; +import * as semver from 'semver'; -const systemCallMethods = {}; - const DEFAULT_ADB_REBOOT_RETRIES = 90; const LINKER_WARNING_REGEXP = /^WARNING: linker.+$/m; const ADB_RETRY_ERROR_PATTERNS = [ @@ -43,21 +41,9 @@ const SUBSYSTEM_STATE_OK = 'Subsystem state: true'; * @param {string} binaryName - The name of the binary. * @return {Promise} Full path to the given binary including current SDK root. */ -systemCallMethods.getSdkBinaryPath = async function getSdkBinaryPath (binaryName) { +export async function getSdkBinaryPath (binaryName) { return await this.getBinaryFromSdkRoot(binaryName); -}; - -/** - * Retrieve full binary name for the current operating system as memotize. - * - * @this {import('../adb.js').ADB} - * @param {string} binaryName - simple binary name, for example 'android'. - * @return {string} Formatted binary name depending on the current platform, - * for example, 'android.bat' on Windows. - */ -systemCallMethods.getBinaryNameForOS = _.memoize(function getBinaryNameForOSMemorize (binaryName) { - return getBinaryNameForOS(binaryName); -}); +} /** * Retrieve full binary name for the current operating system. @@ -66,7 +52,7 @@ systemCallMethods.getBinaryNameForOS = _.memoize(function getBinaryNameForOSMemo * @return {string} Formatted binary name depending on the current platform, * for example, 'android.bat' on Windows. */ -function getBinaryNameForOS (binaryName) { +function _getBinaryNameForOS (binaryName) { if (!system.isWindows()) { return binaryName; } @@ -80,6 +66,8 @@ function getBinaryNameForOS (binaryName) { return binaryName; } +export const getBinaryNameForOS = _.memoize(_getBinaryNameForOS); + /** * Retrieve full path to the given binary and caches it into `binaries` * property of the current ADB instance. @@ -94,7 +82,7 @@ function getBinaryNameForOS (binaryName) { * of known locations or Android SDK is not installed on the * local file system. */ -systemCallMethods.getBinaryFromSdkRoot = async function getBinaryFromSdkRoot (binaryName) { +export async function getBinaryFromSdkRoot (binaryName) { if ((/** @type {import('@appium/types').StringRecord} */ (this.binaries))[binaryName]) { return (/** @type {import('@appium/types').StringRecord} */ (this.binaries))[binaryName]; } @@ -136,7 +124,7 @@ systemCallMethods.getBinaryFromSdkRoot = async function getBinaryFromSdkRoot (bi log.info(`Using '${fullBinaryName}' from '${binaryLoc}'`); (/** @type {import('@appium/types').StringRecord} */ (this.binaries))[binaryName] = binaryLoc; return binaryLoc; -}; +} /** * Returns the Android binaries locations @@ -166,7 +154,7 @@ function getSdkBinaryLocationCandidates (sdkRoot, fullBinaryName) { * of known locations or Android SDK is not installed on the * local file system. */ -async function getAndroidBinaryPath (binaryName) { +export async function getAndroidBinaryPath (binaryName) { const fullBinaryName = getBinaryNameForOS(binaryName); const sdkRoot = getSdkRootFromEnv(); const binaryLocs = getSdkBinaryLocationCandidates(sdkRoot ?? '', fullBinaryName); @@ -188,7 +176,7 @@ async function getAndroidBinaryPath (binaryName) { * output. * @throws {Error} If lookup tool returns non-zero return code. */ -systemCallMethods.getBinaryFromPath = async function getBinaryFromPath (binaryName) { +export async function getBinaryFromPath (binaryName) { if ((/** @type {import('@appium/types').StringRecord} */ (this.binaries))[binaryName]) { return (/** @type {import('@appium/types').StringRecord} */ (this.binaries))[binaryName]; } @@ -199,11 +187,11 @@ systemCallMethods.getBinaryFromPath = async function getBinaryFromPath (binaryNa log.info(`Using '${fullBinaryName}' from '${binaryLoc}'`); (/** @type {import('@appium/types').StringRecord} */ (this.binaries))[binaryName] = binaryLoc; return binaryLoc; - } catch (e) { + } catch { throw new Error(`Could not find '${fullBinaryName}' in PATH. Please set the ANDROID_HOME ` + `or ANDROID_SDK_ROOT environment variables to the correct Android SDK root directory path.`); } -}; +} /** * @typedef {Object} ConnectedDevicesOptions @@ -237,7 +225,7 @@ systemCallMethods.getBinaryFromPath = async function getBinaryFromPath (binaryNa * no devices are connected. * @throws {Error} If there was an error while listing devices. */ -systemCallMethods.getConnectedDevices = async function getConnectedDevices (opts = {}) { +export async function getConnectedDevices (opts = {}) { log.debug('Getting connected devices'); const args = [...this.executable.defaultArgs, 'devices']; if (opts.verbose) { @@ -288,7 +276,7 @@ systemCallMethods.getConnectedDevices = async function getConnectedDevices (opts log.debug(`Connected devices: ${JSON.stringify(devices)}`); } return devices; -}; +} /** * Retrieve the list of devices visible to adb within the given timeout. @@ -299,7 +287,7 @@ systemCallMethods.getConnectedDevices = async function getConnectedDevices (opts * @return {Promise} The list of connected devices. * @throws {Error} If no connected devices can be detected within the given timeout. */ -systemCallMethods.getDevicesWithRetry = async function getDevicesWithRetry (timeoutMs = 20000) { +export async function getDevicesWithRetry (timeoutMs = 20000) { log.debug('Trying to find connected Android devices'); try { let devices; @@ -317,7 +305,7 @@ systemCallMethods.getDevicesWithRetry = async function getDevicesWithRetry (time try { await this.reconnect(); - } catch (ign) { + } catch { await this.restartAdb(); } return false; @@ -333,7 +321,7 @@ systemCallMethods.getDevicesWithRetry = async function getDevicesWithRetry (time throw e; } } -}; +} /** * Kick current connection from host/device side and make it reconnect @@ -346,7 +334,7 @@ systemCallMethods.getDevicesWithRetry = async function getDevicesWithRetry (time * @throws {Error} If either ADB version is too old and does not support this * command or there was a failure during reconnect. */ -systemCallMethods.reconnect = async function reconnect (target = 'offline') { +export async function reconnect (target = 'offline') { log.debug(`Reconnecting adb (target ${target})`); const args = ['reconnect']; @@ -358,14 +346,14 @@ systemCallMethods.reconnect = async function reconnect (target = 'offline') { } catch (e) { throw new Error(`Cannot reconnect adb. Original error: ${e.stderr || e.message}`); } -}; +} /** * Restart adb server, unless _this.suppressKillServer_ property is true. * * @this {import('../adb.js').ADB} */ -systemCallMethods.restartAdb = async function restartAdb () { +export async function restartAdb () { if (this.suppressKillServer) { log.debug(`Not restarting abd since 'suppressKillServer' is on`); return; @@ -375,21 +363,21 @@ systemCallMethods.restartAdb = async function restartAdb () { try { await this.killServer(); await this.adbExec(['start-server']); - } catch (e) { + } catch { log.error(`Error killing ADB server, going to see if it's online anyway`); } -}; +} /** * Kill adb server. * @this {import('../adb.js').ADB} */ -systemCallMethods.killServer = async function killServer () { +export async function killServer () { log.debug(`Killing adb server on port '${this.adbPort}'`); await this.adbExec(['kill-server'], { exclusive: true, }); -}; +} /** * Reset Telnet authentication token. @@ -398,7 +386,7 @@ systemCallMethods.killServer = async function killServer () { * @this {import('../adb.js').ADB} * @returns {Promise} If token reset was successful. */ -systemCallMethods.resetTelnetAuthToken = _.memoize(async function resetTelnetAuthToken () { +export const resetTelnetAuthToken = _.memoize(async function resetTelnetAuthToken () { // The methods is used to remove telnet auth token // const homeFolderPath = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME']; @@ -423,17 +411,17 @@ systemCallMethods.resetTelnetAuthToken = _.memoize(async function resetTelnetAut * @this {import('../adb.js').ADB} * @param {string[]} cmd - The array of rest command line parameters. */ -systemCallMethods.adbExecEmu = async function adbExecEmu (cmd) { +export async function adbExecEmu (cmd) { await this.verifyEmulatorConnected(); await this.resetTelnetAuthToken(); await this.adbExec(['emu', ...cmd]); -}; +} let isExecLocked = false; /** @typedef {'stdout'|'full'} ExecOutputFormat */ /** @type {{STDOUT: 'stdout', FULL: 'full'}} */ -systemCallMethods.EXEC_OUTPUT_FORMAT = Object.freeze({ +export const EXEC_OUTPUT_FORMAT = Object.freeze({ STDOUT: 'stdout', FULL: 'full', }); @@ -486,7 +474,7 @@ systemCallMethods.EXEC_OUTPUT_FORMAT = Object.freeze({ * Command's stdout or an object containing stdout and stderr. * @throws {Error} If the command returned non-zero exit code. */ -systemCallMethods.adbExec = async function adbExec (cmd, opts) { +export async function adbExec (cmd, opts) { if (!cmd) { throw new Error('You need to pass in a command to adbExec()'); } @@ -558,7 +546,7 @@ systemCallMethods.adbExec = async function adbExec (cmd, opts) { isExecLocked = false; } } -}; +} /** * Execute the given command using _adb shell_ prefix. @@ -572,7 +560,7 @@ systemCallMethods.adbExec = async function adbExec (cmd, opts) { * Command's stdout. * @throws {Error} If the command returned non-zero exit code. */ -systemCallMethods.shell = async function shell (cmd, opts) { +export async function shell (cmd, opts) { const { privileged, } = opts ?? /** @type {TShellExecOpts} */ ({}); @@ -591,7 +579,7 @@ systemCallMethods.shell = async function shell (cmd, opts) { fullCmd.push(...cmdArr); } return await this.adbExec(fullCmd, opts); -}; +} /** * @@ -599,12 +587,12 @@ systemCallMethods.shell = async function shell (cmd, opts) { * @param {string[]} [args=[]] * @returns {import('teen_process').SubProcess} */ -systemCallMethods.createSubProcess = function createSubProcess (args = []) { +export function createSubProcess (args = []) { // add the default arguments const finalArgs = [...this.executable.defaultArgs, ...args]; log.debug(`Creating ADB subprocess with args: ${JSON.stringify(finalArgs)}`); return new SubProcess(this.getAdbPath(), finalArgs); -}; +} /** * Retrieve the current adb port. @@ -613,9 +601,9 @@ systemCallMethods.createSubProcess = function createSubProcess (args = []) { * @this {import('../adb.js').ADB} * @return {number} The current adb port number. */ -systemCallMethods.getAdbServerPort = function getAdbServerPort () { +export function getAdbServerPort () { return /** @type {number} */ (this.adbPort); -}; +} /** * Retrieve the current emulator port from _adb devives_ output. @@ -624,7 +612,7 @@ systemCallMethods.getAdbServerPort = function getAdbServerPort () { * @return {Promise} The current emulator port. * @throws {Error} If there are no connected devices. */ -systemCallMethods.getEmulatorPort = async function getEmulatorPort () { +export async function getEmulatorPort () { log.debug('Getting running emulator port'); if (this.emulatorPort !== null) { return /** @type {number} */ (this.emulatorPort); @@ -640,7 +628,7 @@ systemCallMethods.getEmulatorPort = async function getEmulatorPort () { } catch (e) { throw new Error(`No devices connected. Original error: ${e.message}`); } -}; +} /** * Retrieve the current emulator port by parsing emulator name string. @@ -650,13 +638,13 @@ systemCallMethods.getEmulatorPort = async function getEmulatorPort () { * @return {number|false} Either the current emulator port or * _false_ if port number cannot be parsed. */ -systemCallMethods.getPortFromEmulatorString = function getPortFromEmulatorString (emStr) { +export function getPortFromEmulatorString (emStr) { let portPattern = /emulator-(\d+)/; if (portPattern.test(emStr)) { return parseInt((/** @type {RegExpExecArray} */(portPattern.exec(emStr)))[1], 10); } return false; -}; +} /** * Retrieve the list of currently connected emulators. @@ -665,7 +653,7 @@ systemCallMethods.getPortFromEmulatorString = function getPortFromEmulatorString * @param {ConnectedDevicesOptions} [opts={}] - Additional options mapping. * @return {Promise} The list of connected devices. */ -systemCallMethods.getConnectedEmulators = async function getConnectedEmulators (opts = {}) { +export async function getConnectedEmulators (opts = {}) { log.debug('Getting connected emulators'); try { let devices = await this.getConnectedDevices(opts); @@ -682,7 +670,7 @@ systemCallMethods.getConnectedEmulators = async function getConnectedEmulators ( } catch (e) { throw new Error(`Error getting emulators. Original error: ${e.message}`); } -}; +} /** * Set _emulatorPort_ property of the current class. @@ -690,9 +678,9 @@ systemCallMethods.getConnectedEmulators = async function getConnectedEmulators ( * @this {import('../adb.js').ADB} * @param {number} emPort - The emulator port to be set. */ -systemCallMethods.setEmulatorPort = function setEmulatorPort (emPort) { +export function setEmulatorPort (emPort) { this.emulatorPort = emPort; -}; +} /** * Set the identifier of the current device (_this.curDeviceId_). @@ -700,7 +688,7 @@ systemCallMethods.setEmulatorPort = function setEmulatorPort (emPort) { * @this {import('../adb.js').ADB} * @param {string} deviceId - The device identifier. */ -systemCallMethods.setDeviceId = function setDeviceId (deviceId) { +export function setDeviceId (deviceId) { log.debug(`Setting device id to ${deviceId}`); this.curDeviceId = deviceId; let argsHasDevice = this.executable.defaultArgs.indexOf('-s'); @@ -709,7 +697,7 @@ systemCallMethods.setDeviceId = function setDeviceId (deviceId) { this.executable.defaultArgs.splice(argsHasDevice, 2); } this.executable.defaultArgs.push('-s', deviceId); -}; +} /** * Set the the current device object. @@ -717,14 +705,14 @@ systemCallMethods.setDeviceId = function setDeviceId (deviceId) { * @this {import('../adb.js').ADB} * @param {Device} deviceObj - The device object to be set. */ -systemCallMethods.setDevice = function setDevice (deviceObj) { +export function setDevice (deviceObj) { const deviceId = deviceObj.udid; const emPort = this.getPortFromEmulatorString(deviceId); if (_.isNumber(emPort)) { this.setEmulatorPort(emPort); } this.setDeviceId(deviceId); -}; +} /** * Get the object for the currently running emulator. @@ -736,7 +724,7 @@ systemCallMethods.setDevice = function setDevice (deviceObj) { * @param {string} avdName - Emulator name. * @return {Promise} Currently running emulator or _null_. */ -systemCallMethods.getRunningAVD = async function getRunningAVD (avdName) { +export async function getRunningAVD (avdName) { log.debug(`Trying to find '${avdName}' emulator`); try { const emulators = await this.getConnectedEmulators(); @@ -760,7 +748,7 @@ systemCallMethods.getRunningAVD = async function getRunningAVD (avdName) { } catch (e) { throw new Error(`Error getting AVD. Original error: ${e.message}`); } -}; +} /** * Get the object for the currently running emulator. @@ -773,7 +761,7 @@ systemCallMethods.getRunningAVD = async function getRunningAVD (avdName) { * @return {Promise} Currently running emulator or _null_. * @throws {Error} If no device has been detected within the timeout. */ -systemCallMethods.getRunningAVDWithRetry = async function getRunningAVDWithRetry (avdName, timeoutMs = 20000) { +export async function getRunningAVDWithRetry (avdName, timeoutMs = 20000) { try { return /** @type {Device|null} */ (await waitForCondition(async () => { try { @@ -789,7 +777,7 @@ systemCallMethods.getRunningAVDWithRetry = async function getRunningAVDWithRetry } catch (e) { throw new Error(`Error getting AVD with retry. Original error: ${e.message}`); } -}; +} /** * Shutdown all running emulators by killing their processes. @@ -797,7 +785,7 @@ systemCallMethods.getRunningAVDWithRetry = async function getRunningAVDWithRetry * @this {import('../adb.js').ADB} * @throws {Error} If killing tool returned non-zero return code. */ -systemCallMethods.killAllEmulators = async function killAllEmulators () { +export async function killAllEmulators () { let cmd, args; if (system.isWindows()) { cmd = 'TASKKILL'; @@ -811,7 +799,7 @@ systemCallMethods.killAllEmulators = async function killAllEmulators () { } catch (e) { throw new Error(`Error killing emulators. Original error: ${e.message}`); } -}; +} /** * Kill emulator with the given name. No error @@ -825,7 +813,7 @@ systemCallMethods.killAllEmulators = async function killAllEmulators () { * @return {Promise} - True if the emulator was killed, false otherwise. * @throws {Error} if there was a failure by killing the emulator */ -systemCallMethods.killEmulator = async function killEmulator (avdName = null, timeout = 60000) { +export async function killEmulator (avdName = null, timeout = 60000) { if (util.hasValue(avdName)) { log.debug(`Killing avd '${avdName}'`); const device = await this.getRunningAVD(avdName); @@ -849,18 +837,18 @@ systemCallMethods.killEmulator = async function killEmulator (avdName = null, ti return util.hasValue(avdName) ? !await this.getRunningAVD(avdName) : !await this.isEmulatorConnected(); - } catch (ign) {} + } catch {} return false; }, { waitMs: timeout, intervalMs: 2000, }); - } catch (e) { + } catch { throw new Error(`The emulator '${avdName ? avdName : this.curDeviceId}' is still running after being killed ${timeout}ms ago`); } log.info(`Successfully killed the '${avdName ? avdName : this.curDeviceId}' emulator`); return true; -}; +} /** * @typedef {Object} AvdLaunchOptions @@ -883,7 +871,7 @@ systemCallMethods.killEmulator = async function killEmulator (avdName = null, ti * @returns {Promise} Emulator subprocess instance * @throws {Error} If the emulator fails to start within the given timeout. */ -systemCallMethods.launchAVD = async function launchAVD (avdName, opts = {}) { +export async function launchAVD (avdName, opts = {}) { const { args = [], env = {}, @@ -959,7 +947,7 @@ systemCallMethods.launchAVD = async function launchAVD (avdName, opts = {}) { } await this.waitForEmulatorReady(Math.trunc(readyTimeout - timer.getDuration().asMilliSeconds)); return proc; -}; +} /** * @typedef {Object} BinaryVersion @@ -986,7 +974,7 @@ systemCallMethods.launchAVD = async function launchAVD (avdName, opts = {}) { * @return {Promise} * @throws {Error} If it is not possible to parse adb binary version. */ -systemCallMethods.getVersion = _.memoize(async function getVersion () { +export const getVersion = _.memoize(async function getVersion () { let stdout; try { stdout = await this.adbExec('version'); @@ -1019,7 +1007,7 @@ systemCallMethods.getVersion = _.memoize(async function getVersion () { * @returns {Promise} * @throws {Error} If the emulator is not ready within the given timeout. */ -systemCallMethods.waitForEmulatorReady = async function waitForEmulatorReady (timeoutMs = 20000) { +export async function waitForEmulatorReady (timeoutMs = 20000) { log.debug(`Waiting up to ${timeoutMs}ms for the emulator to be ready`); if (await this.getApiLevel() >= 31) { /** @type {string|undefined} */ @@ -1044,7 +1032,7 @@ systemCallMethods.waitForEmulatorReady = async function waitForEmulatorReady (ti waitMs: timeoutMs, intervalMs: 1000, }); - } catch (e) { + } catch { throw new Error(`Emulator is not ready within ${timeoutMs}ms${state ? ('. Reason: ' + state) : ''}`); } return; @@ -1066,7 +1054,7 @@ systemCallMethods.waitForEmulatorReady = async function waitForEmulatorReady (ti waitMs: timeoutMs, intervalMs: 3000, }); - } catch (e) { + } catch { if (services) { log.debug(`Recently listed services:\n${services}`); } @@ -1076,7 +1064,7 @@ systemCallMethods.waitForEmulatorReady = async function waitForEmulatorReady (ti throw new Error(`Emulator is not ready within ${timeoutMs}ms ` + `(${missingServices} service${missingServices.length === 1 ? ' is' : 's are'} not running)`); } -}; +} /** * Check if the current device is ready to accept further commands (booting completed). @@ -1085,7 +1073,7 @@ systemCallMethods.waitForEmulatorReady = async function waitForEmulatorReady (ti * @param {number} [appDeviceReadyTimeout=30] - The maximum number of seconds to wait. * @throws {Error} If the device is not ready within the given timeout. */ -systemCallMethods.waitForDevice = async function waitForDevice (appDeviceReadyTimeout = 30) { +export async function waitForDevice (appDeviceReadyTimeout = 30) { this.appDeviceReadyTimeout = appDeviceReadyTimeout; const retries = 3; const timeout = parseInt(`${this.appDeviceReadyTimeout}`, 10) * 1000 / retries; @@ -1096,14 +1084,14 @@ systemCallMethods.waitForDevice = async function waitForDevice (appDeviceReadyTi } catch (e) { try { await this.reconnect(); - } catch (ign) { + } catch { await this.restartAdb(); } await this.getConnectedDevices(); throw new Error(`Error waiting for the device to be available. Original error: '${e.message}'`); } }); -}; +} /** * Reboot the current device and wait until it is completed. @@ -1112,7 +1100,7 @@ systemCallMethods.waitForDevice = async function waitForDevice (appDeviceReadyTi * @param {number} [retries=DEFAULT_ADB_REBOOT_RETRIES] - The maximum number of reboot retries. * @throws {Error} If the device failed to reboot and number of retries is exceeded. */ -systemCallMethods.reboot = async function reboot (retries = DEFAULT_ADB_REBOOT_RETRIES) { +export async function reboot (retries = DEFAULT_ADB_REBOOT_RETRIES) { // Get root access so we can run the next shell commands which require root access const { wasAlreadyRooted } = await this.root(); try { @@ -1148,7 +1136,7 @@ systemCallMethods.reboot = async function reboot (retries = DEFAULT_ADB_REBOOT_R log.debug(msg); throw new Error(msg); }); -}; +} /** * @typedef {Object} RootResult @@ -1163,7 +1151,7 @@ systemCallMethods.reboot = async function reboot (retries = DEFAULT_ADB_REBOOT_R * @param {boolean} isElevated - Should we elevate to to root or unroot? (default true) * @return {Promise} */ -systemCallMethods.changeUserPrivileges = async function changeUserPrivileges (isElevated) { +export async function changeUserPrivileges (isElevated) { const cmd = isElevated ? 'root' : 'unroot'; const retryIfOffline = async (cmdFunc) => { @@ -1177,7 +1165,7 @@ systemCallMethods.changeUserPrivileges = async function changeUserPrivileges (is log.warn(`Attempt to ${cmd} caused ADB to think the device went offline`); try { await this.reconnect(); - } catch (ign) { + } catch { await this.restartAdb(); } return await cmdFunc(); @@ -1214,7 +1202,7 @@ systemCallMethods.changeUserPrivileges = async function changeUserPrivileges (is log.warn(`Unable to ${cmd} adb daemon. Original error: '${message}'. Stderr: '${stderr}'. Continuing.`); return {isSuccessful: false, wasAlreadyRooted}; } -}; +} /** * Switch adb server to root mode @@ -1222,9 +1210,9 @@ systemCallMethods.changeUserPrivileges = async function changeUserPrivileges (is * @this {import('../adb.js').ADB} * @return {Promise} */ -systemCallMethods.root = async function root () { +export async function root () { return await this.changeUserPrivileges(true); -}; +} /** * Switch adb server to non-root mode. @@ -1232,9 +1220,9 @@ systemCallMethods.root = async function root () { * @this {import('../adb.js').ADB} * @return {Promise} */ -systemCallMethods.unroot = async function unroot () { +export async function unroot () { return await this.changeUserPrivileges(false); -}; +} /** * Checks whether the current user is root @@ -1244,9 +1232,9 @@ systemCallMethods.unroot = async function unroot () { * @throws {Error} if there was an error while identifying * the user. */ -systemCallMethods.isRoot = async function isRoot () { +export async function isRoot () { return (await this.shell(['whoami'])).trim() === 'root'; -}; +} /** * Verify whether a remote path exists on the device under test. @@ -1255,15 +1243,15 @@ systemCallMethods.isRoot = async function isRoot () { * @param {string} remotePath - The remote path to verify. * @return {Promise} True if the given path exists on the device. */ -systemCallMethods.fileExists = async function fileExists (remotePath) { +export async function fileExists (remotePath) { const passFlag = '__PASS__'; const checkCmd = `[ -e '${remotePath.replace(/'/g, `\\'`)}' ] && echo ${passFlag}`; try { return _.includes(await this.shell([checkCmd]), passFlag); - } catch (ign) { + } catch { return false; } -}; +} /** * Get the output of _ls_ command on the device under test. @@ -1275,7 +1263,7 @@ systemCallMethods.fileExists = async function fileExists (remotePath) { * An empty array is returned of the given _remotePath_ * does not exist. */ -systemCallMethods.ls = async function ls (remotePath, opts = []) { +export async function ls (remotePath, opts = []) { try { let args = ['ls', ...opts, remotePath]; let stdout = await this.shell(args); @@ -1289,7 +1277,7 @@ systemCallMethods.ls = async function ls (remotePath, opts = []) { } return []; } -}; +} /** * Get the size of the particular file located on the device under test. @@ -1299,7 +1287,7 @@ systemCallMethods.ls = async function ls (remotePath, opts = []) { * @return {Promise} File size in bytes. * @throws {Error} If there was an error while getting the size of the given file. */ -systemCallMethods.fileSize = async function fileSize (remotePath) { +export async function fileSize (remotePath) { try { const files = await this.ls(remotePath, ['-la']); if (files.length !== 1) { @@ -1314,7 +1302,7 @@ systemCallMethods.fileSize = async function fileSize (remotePath) { } catch (err) { throw new Error(`Unable to get file size for '${remotePath}': ${err.message}`); } -}; +} /** * Installs the given certificate on a rooted real device or @@ -1331,7 +1319,7 @@ systemCallMethods.fileSize = async function fileSize (remotePath) { * @throws {Error} If openssl tool is not available on the destination system * or if there was an error while installing the certificate */ -systemCallMethods.installMitmCertificate = async function installMitmCertificate (cert) { +export async function installMitmCertificate (cert) { const openSsl = await getOpenSslForOs(); const tmpRoot = await tempDir.openDir(); @@ -1368,7 +1356,7 @@ systemCallMethods.installMitmCertificate = async function installMitmCertificate } finally { await fs.rimraf(tmpRoot); } -}; +} /** * Verifies if the given root certificate is already installed on the device. @@ -1380,7 +1368,7 @@ systemCallMethods.installMitmCertificate = async function installMitmCertificate * or if there was an error while checking the certificate * @returns {Promise} true if the given certificate is already installed */ -systemCallMethods.isMitmCertificateInstalled = async function isMitmCertificateInstalled (cert) { +export async function isMitmCertificateInstalled (cert) { const openSsl = await getOpenSslForOs(); const tmpRoot = await tempDir.openDir(); @@ -1400,11 +1388,4 @@ systemCallMethods.isMitmCertificateInstalled = async function isMitmCertificateI const dstPath = path.posix.resolve(CERTS_ROOT, `${certHash}.0`); log.debug(`Checking if the certificate is already installed at '${dstPath}'`); return await this.fileExists(dstPath); -}; - -export default systemCallMethods; -export { DEFAULT_ADB_EXEC_TIMEOUT, getAndroidBinaryPath }; - -/** - * @typedef {typeof systemCallMethods} SystemCalls - */ +} diff --git a/package.json b/package.json index ac9c11e8..f51432dd 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "homepage": "https://github.com/appium/appium-adb", "dependencies": { "@appium/support": "^6.0.0", + "@appium/types": "^0.x", "async-lock": "^1.0.0", "asyncbox": "^3.0.0", "bluebird": "^3.4.7", @@ -59,9 +60,8 @@ "teen_process": "^2.2.0" }, "devDependencies": { - "@appium/eslint-config-appium-ts": "^0.x", + "@appium/eslint-config-appium-ts": "^1.x", "@appium/test-support": "^3.0.1", - "@appium/types": "^0.x", "@semantic-release/changelog": "^6.0.1", "@semantic-release/git": "^10.0.1", "@types/async-lock": "^1.4.0", diff --git a/test/functional/adb-commands-e2e-specs.js b/test/functional/adb-commands-e2e-specs.js index 6bcc6523..d3dd4f44 100644 --- a/test/functional/adb-commands-e2e-specs.js +++ b/test/functional/adb-commands-e2e-specs.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-unresolved import {ADB} from '../../lib/adb'; import path from 'path'; import { apiLevel, platformVersion, MOCHA_TIMEOUT } from './setup'; diff --git a/test/functional/adb-e2e-specs.js b/test/functional/adb-e2e-specs.js index 6ab77976..c72db3f9 100644 --- a/test/functional/adb-e2e-specs.js +++ b/test/functional/adb-e2e-specs.js @@ -1,5 +1,4 @@ import _ from 'lodash'; -// eslint-disable-next-line import/no-unresolved import {ADB} from '../../lib/adb'; import { fs } from '@appium/support'; import path from 'path'; diff --git a/test/functional/adb-emu-commands-e2e-specs.js b/test/functional/adb-emu-commands-e2e-specs.js index 28056b54..23447e82 100644 --- a/test/functional/adb-emu-commands-e2e-specs.js +++ b/test/functional/adb-emu-commands-e2e-specs.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-unresolved import {ADB} from '../../lib/adb'; describe('adb emu commands', function () { diff --git a/test/functional/android-manifest-e2e-specs.js b/test/functional/android-manifest-e2e-specs.js index 5ad1944c..ad26b9e3 100644 --- a/test/functional/android-manifest-e2e-specs.js +++ b/test/functional/android-manifest-e2e-specs.js @@ -1,7 +1,6 @@ -// eslint-disable-next-line import/no-unresolved import {ADB} from '../../lib/adb'; import path from 'path'; -import { fs, util } from '@appium/support'; +import { fs } from '@appium/support'; // All paths below assume tests run under /build/test/ so paths are relative from @@ -55,9 +54,9 @@ describe('Android-manifest', function () { await fs.mkdir(dstDir); await fs.writeFile(dstManifest, await fs.readFile(srcManifest, 'utf8'), 'utf8'); await adb.compileManifest(dstManifest, newPackage, appPackage); - (await util.fileExists(dstManifest)).should.be.true; + (await fs.fileExists(dstManifest)).should.be.true; await adb.insertManifest(dstManifest, serverPath, newServerPath); - (await util.fileExists(newServerPath)).should.be.true; + (await fs.fileExists(newServerPath)).should.be.true; // deleting temp directory try { await fs.rimraf(tmpDir); diff --git a/test/functional/apk-signing-e2e-specs.js b/test/functional/apk-signing-e2e-specs.js index ba3dc871..8697bc76 100644 --- a/test/functional/apk-signing-e2e-specs.js +++ b/test/functional/apk-signing-e2e-specs.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-unresolved import {ADB} from '../../lib/adb'; import path from 'path'; import os from 'os'; diff --git a/test/functional/apk-utils-e2e-specs.js b/test/functional/apk-utils-e2e-specs.js index da056c16..4bc1d732 100644 --- a/test/functional/apk-utils-e2e-specs.js +++ b/test/functional/apk-utils-e2e-specs.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-unresolved import {ADB} from '../../lib/adb'; import path from 'path'; import { retryInterval } from 'asyncbox'; diff --git a/test/functional/helpers-specs-e2e-specs.js b/test/functional/helpers-specs-e2e-specs.js index 29f6b33b..489a435d 100644 --- a/test/functional/helpers-specs-e2e-specs.js +++ b/test/functional/helpers-specs-e2e-specs.js @@ -3,7 +3,6 @@ import { requireSdkRoot, readPackageManifest, } from '../../lib/helpers.js'; -// eslint-disable-next-line import/no-unresolved import {ADB} from '../../lib/adb'; import path from 'node:path'; diff --git a/test/functional/lock-mgmt-e2e-specs.js b/test/functional/lock-mgmt-e2e-specs.js index 5e3cc72c..1859de7c 100644 --- a/test/functional/lock-mgmt-e2e-specs.js +++ b/test/functional/lock-mgmt-e2e-specs.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-unresolved import {ADB} from '../../lib/adb'; describe('Lock Management', function () { @@ -11,14 +10,13 @@ describe('Lock Management', function () { chai.should(); chai.use(chaiAsPromised.default); - }); - before(async function () { adb = await ADB.createADB(); if (!await adb.isLockManagementSupported()) { return this.skip(); } }); + it('lock credential cleanup should work', async function () { await adb.clearLockCredential(); await adb.verifyLockCredential().should.eventually.be.true; diff --git a/test/functional/logcat-e2e-specs.js b/test/functional/logcat-e2e-specs.js index c0886585..bab03c75 100644 --- a/test/functional/logcat-e2e-specs.js +++ b/test/functional/logcat-e2e-specs.js @@ -1,6 +1,5 @@ -// eslint-disable-next-line import/no-unresolved import {ADB} from '../../lib/adb'; -import Logcat from '../../lib/logcat'; +import { Logcat } from '../../lib/logcat'; import { MOCHA_TIMEOUT } from './setup'; describe('logcat', function () { diff --git a/test/functional/syscalls-e2e-specs.js b/test/functional/syscalls-e2e-specs.js index afa2496c..23730254 100644 --- a/test/functional/syscalls-e2e-specs.js +++ b/test/functional/syscalls-e2e-specs.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-unresolved import {ADB} from '../../lib/adb'; import { apiLevel, avdName, MOCHA_TIMEOUT, MOCHA_LONG_TIMEOUT } from './setup'; import path from 'path'; diff --git a/test/unit/adb-commands-specs.js b/test/unit/adb-commands-specs.js index 2951ee19..281c266a 100644 --- a/test/unit/adb-commands-specs.js +++ b/test/unit/adb-commands-specs.js @@ -1,7 +1,6 @@ -// eslint-disable-next-line import/no-unresolved import {ADB} from '../../lib/adb'; import net from 'net'; -import Logcat from '../../lib/logcat.js'; +import { Logcat } from '../../lib/logcat.js'; import * as teen_process from 'teen_process'; import { withMocks } from '@appium/test-support'; import _ from 'lodash'; diff --git a/test/unit/adb-emu-commands-specs.js b/test/unit/adb-emu-commands-specs.js index 93fdbae2..f3cba1b5 100644 --- a/test/unit/adb-emu-commands-specs.js +++ b/test/unit/adb-emu-commands-specs.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-unresolved import {ADB} from '../../lib/adb'; import { withMocks } from '@appium/test-support'; import _ from 'lodash'; diff --git a/test/unit/adb-specs.js b/test/unit/adb-specs.js index d3fd76af..067aa48b 100644 --- a/test/unit/adb-specs.js +++ b/test/unit/adb-specs.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-unresolved import { ADB, DEFAULT_ADB_PORT } from '../../lib/adb'; diff --git a/test/unit/apk-signing-specs.js b/test/unit/apk-signing-specs.js index ad5d3ecd..d59d86b7 100644 --- a/test/unit/apk-signing-specs.js +++ b/test/unit/apk-signing-specs.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-unresolved import {ADB} from '../../lib/adb'; import * as helpers from '../../lib/helpers.js'; import path from 'path'; diff --git a/test/unit/apk-utils-specs.js b/test/unit/apk-utils-specs.js index d573797d..619a8e2e 100644 --- a/test/unit/apk-utils-specs.js +++ b/test/unit/apk-utils-specs.js @@ -1,12 +1,11 @@ import * as teen_process from 'teen_process'; import { fs } from '@appium/support'; -// eslint-disable-next-line import/no-unresolved import {ADB} from '../../lib/adb'; import { withMocks } from '@appium/test-support'; import _ from 'lodash'; import B from 'bluebird'; import { REMOTE_CACHE_ROOT } from '../../lib/tools/apk-utils'; -import apksUtilsMethods from '../../lib/tools/apks-utils'; +import * as apksUtilsMethods from '../../lib/tools/apks-utils'; const pkg = 'com.example.android.contactmanager', uri = 'content://contacts/people/1', @@ -99,7 +98,7 @@ describe('Apk-utils', withMocks({adb, fs, teen_process}, function (mocks) { .returns(`package:/system/priv-app/TeleService/TeleService.apk with user`); (await adb.isAppInstalled(pkg, {user: '1'})).should.be.true; }); - it('should parse correctly and return false for older versions', async function () { + it('should parse correctly and return false for older versions for user', async function () { const pkg = 'dummy.package'; mocks.adb.expects('getApiLevel') .returns(25); @@ -561,7 +560,7 @@ describe('Apk-utils', withMocks({adb, fs, teen_process}, function (mocks) { .returns(''); (await adb.startApp(startAppOptions)); }); - it('should call getApiLevel and shell with correct arguments', async function () { + it('should call getApiLevel and shell with correct arguments for class error', async function () { mocks.adb.expects('getApiLevel') .twice() .returns(17); diff --git a/test/unit/logcat-specs.js b/test/unit/logcat-specs.js index 2ff9c31c..1262b23d 100644 --- a/test/unit/logcat-specs.js +++ b/test/unit/logcat-specs.js @@ -1,6 +1,6 @@ import * as teen_process from 'teen_process'; import events from 'events'; -import Logcat from '../../lib/logcat'; +import { Logcat } from '../../lib/logcat'; import { withMocks } from '@appium/test-support'; describe('logcat', withMocks({teen_process}, function (mocks) { diff --git a/test/unit/syscalls-specs.js b/test/unit/syscalls-specs.js index bd7525d4..257dc1bb 100644 --- a/test/unit/syscalls-specs.js +++ b/test/unit/syscalls-specs.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/no-unresolved import {ADB} from '../../lib/adb'; import * as teen_process from 'teen_process'; import { withMocks } from '@appium/test-support'; @@ -136,7 +135,7 @@ describe('System calls', withMocks({teen_process}, function (mocks) { }); })); -describe('System calls', withMocks({adb, B, teen_process}, function (mocks) { +describe('System calls 2', withMocks({adb, B, teen_process}, function (mocks) { let chai; before(async function () {