diff --git a/index.ts b/index.ts index e2158d0c..ecdd861c 100644 --- a/index.ts +++ b/index.ts @@ -1,30 +1,11 @@ -/** - * @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 {install} from 'source-map-support'; install(); export * from './lib/adb'; -// 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'; +export type * from './lib/tools/types'; +export type * from './lib/types'; import {ADB} from './lib/adb'; export default ADB; diff --git a/lib/adb.ts b/lib/adb.ts index 2306b377..3f8ab53a 100644 --- a/lib/adb.ts +++ b/lib/adb.ts @@ -6,11 +6,11 @@ import { getSdkRootFromEnv } from './helpers'; import log from './logger'; -import type {ADBOptions, ADBExecutable} from './options'; -import type { LogcatOpts, Logcat } from './logcat'; +import type { ADBOptions, ADBExecutable } from './types'; +import type { Logcat } from './logcat'; +import type { LogcatOpts, StringRecord } from './tools/types'; import type { LRUCache } from 'lru-cache'; import type { ExecError } from 'teen_process'; -import type { StringRecord } from '@appium/types'; import * as generalMethods from './tools/adb-commands'; import * as manifestMethods from './tools/android-manifest'; @@ -22,6 +22,7 @@ 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'; +import * as emuConstants from './tools/emu-constants'; export const DEFAULT_ADB_PORT = 5037; @@ -362,10 +363,10 @@ export class ADB implements ADBOptions { 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; + POWER_AC_STATES = emuConstants.POWER_AC_STATES; + GSM_CALL_ACTIONS = emuConstants.GSM_CALL_ACTIONS; + GSM_VOICE_STATES = emuConstants.GSM_VOICE_STATES; + GSM_SIGNAL_STRENGTHS = emuConstants.GSM_SIGNAL_STRENGTHS; + NETWORK_SPEED = emuConstants.NETWORK_SPEED; + SENSORS = emuConstants.SENSORS; } diff --git a/lib/helpers.js b/lib/helpers.js index ef67c324..42f34435 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -93,26 +93,18 @@ export async function requireSdkRoot (customRoot = null) { return /** @type {string} */ (sdkRoot); } -/** - * @typedef {Object} PlatformInfo - * @property {string?} platform - The platform name, for example `android-24` - * or `null` if it cannot be found - * @property {string?} platformPath - Full path to the platform SDK folder - * or `null` if it cannot be found - */ - /** * Retrieve the path to the recent installed Android platform. * * @param {string} sdkRoot - * @return {Promise} The resulting path to the newest installed platform. + * @return {Promise} The resulting path to the newest installed platform. */ export async function getAndroidPlatformAndPath (sdkRoot) { const propsPaths = await fs.glob('*/build.prop', { cwd: path.resolve(sdkRoot, 'platforms'), absolute: true, }); - /** @type {Record} */ + /** @type {Record} */ const platformsMapping = {}; for (const propsPath of propsPaths) { const propsContent = await fs.readFile(propsPath, 'utf-8'); @@ -549,23 +541,6 @@ export const extractMatchingPermissions = function (dumpsysOutput, groupNames, g return filteredResult; }; -/** - * @typedef {Object} InstallOptions - * @property {boolean} [allowTestPackages=false] - Set to true in order to allow test - * packages installation. - * @property {boolean} [useSdcard=false] - Set to true to install the app on sdcard - * instead of the device memory. - * @property {boolean} [grantPermissions=false] - Set to true in order to grant all the - * permissions requested in the application's manifest - * automatically after the installation is completed - * under Android 6+. - * @property {boolean} [replace=true] - Set it to false if you don't want - * the application to be upgraded/reinstalled - * if it is already present on the device. - * @property {boolean} [partialInstall=false] - Install apks partially. It is used for 'install-multiple'. - * https://android.stackexchange.com/questions/111064/what-is-a-partial-application-install-via-adb - */ - /** * Transforms given options into the list of `adb install.install-multiple` command arguments * @@ -1008,42 +983,17 @@ export function matchComponentName (classString) { return /^[\p{L}0-9./_]+$/u.exec(classString); } -/** - * @typedef {Object} LaunchableActivity - * @property {string} name - * @property {string} [label] - * @property {string} [icon] - */ - -/** - * @typedef {Object} ApkManifest - * @property {string} name Package name, for example 'io.appium.android.apis' - * @property {number} versionCode - * @property {string} [versionName] - * @property {string} [platformBuildVersionName] - * @property {number} [platformBuildVersionCode] - * @property {number} compileSdkVersion - * @property {string} [compileSdkVersionCodename] - * @property {number} minSdkVersion - * @property {number} [targetSdkVersion] - * @property {string[]} usesPermissions List of requested permissions - * @property {LaunchableActivity} launchableActivity - * @property {string[]} locales List of supported locales - * @property {string[]} architectures List of supported architectures. Could be empty for older apps. - * @property {number[]} densities List of supported display densities - */ - /** * Extracts various package manifest details * from the given application file. * * @this {import('./adb.js').ADB} * @param {string} apkPath Full path to the application file. - * @returns {Promise} + * @returns {Promise} */ export async function readPackageManifest(apkPath) { await this.initAapt2(); - const aapt2Binary = (/** @type {import('@appium/types').StringRecord} */ (this.binaries)).aapt2; + const aapt2Binary = (/** @type {import('./tools/types').StringRecord} */ (this.binaries)).aapt2; const args = ['dump', 'badging', apkPath]; log.debug(`Reading package manifest: '${util.quote([aapt2Binary, ...args])}'`); @@ -1085,7 +1035,7 @@ export async function readPackageManifest(apkPath) { const toInt = (/** @type {string} */ x) => parseInt(x, 10); - /** @type {ApkManifest} */ + /** @type {import('./tools/types').ApkManifest} */ const result = { name: '', versionCode: 0, @@ -1155,3 +1105,20 @@ export async function readPackageManifest(apkPath) { } return result; } + +/** + * @typedef {Object} InstallOptions + * @property {boolean} [allowTestPackages=false] - Set to true in order to allow test + * packages installation. + * @property {boolean} [useSdcard=false] - Set to true to install the app on sdcard + * instead of the device memory. + * @property {boolean} [grantPermissions=false] - Set to true in order to grant all the + * permissions requested in the application's manifest + * automatically after the installation is completed + * under Android 6+. + * @property {boolean} [replace=true] - Set it to false if you don't want + * the application to be upgraded/reinstalled + * if it is already present on the device. + * @property {boolean} [partialInstall=false] - Install apks partially. It is used for 'install-multiple'. + * https://android.stackexchange.com/questions/111064/what-is-a-partial-application-install-via-adb + */ diff --git a/lib/logcat.js b/lib/logcat.js index 45a780ab..10ac3c82 100644 --- a/lib/logcat.js +++ b/lib/logcat.js @@ -16,27 +16,6 @@ const DEFAULT_FORMAT = 'threadtime'; const TRACE_PATTERN = /W\/Trace/; const EXECVP_ERR_PATTERN = /execvp\(\)/; -/** - * @typedef {Object} LogcatOpts - * @property {string} [format] The log print format, where is one of: - * brief process tag thread raw time threadtime long - * `threadtime` is the default value. - * @property {Array} [filterSpecs] Series of `[:priority]` - * where `` is a log component tag (or `*` for all) and priority is: - * V Verbose - * D Debug - * I Info - * W Warn - * E Error - * F Fatal - * S Silent (supress all output) - * - * `'*'` means `'*:d'` and `` by itself means `:v` - * - * If not specified on the commandline, filterspec is set from `ANDROID_LOG_TAGS`. - * If no filterspec is found, filter defaults to `'*:I'` - */ - function requireFormat (format) { if (!SUPPORTED_FORMATS.includes(format)) { log.info(`The format value '${format}' is unknown. Supported values are: ${SUPPORTED_FORMATS}`); @@ -46,18 +25,11 @@ function requireFormat (format) { return format; } -/** - * @typedef {Object} LogEntry - * @property {number} timestamp - * @property {'ALL'} level - * @property {string} message - */ - /** * * @param {string} message * @param {number} timestamp - * @returns {LogEntry} + * @returns {import('./tools/types').LogEntry} */ function toLogEntry(message, timestamp) { return { @@ -216,10 +188,10 @@ export class Logcat extends EventEmitter { } /** - * @returns {LogEntry[]} + * @returns {import('./tools/types').LogEntry[]} */ getLogs () { - /** @type {LogEntry[]} */ + /** @type {import('./tools/types').LogEntry[]} */ const result = []; /** @type {number?} */ let recentLogIndex = null; @@ -237,10 +209,10 @@ export class Logcat extends EventEmitter { } /** - * @returns {LogEntry[]} + * @returns {import('./tools/types').LogEntry[]} */ getAllLogs () { - /** @type {LogEntry[]} */ + /** @type {import('./tools/types').LogEntry[]} */ const result = []; for (const [message, timestamp] of /** @type {Generator} */ (this.logs.rvalues())) { result.push(toLogEntry(message, timestamp)); diff --git a/lib/options.ts b/lib/options.ts index acb0231c..e69de29b 100644 --- a/lib/options.ts +++ b/lib/options.ts @@ -1,35 +0,0 @@ -import type {Logcat} from './logcat'; -import type {StringRecord} from '@appium/types'; - -export interface ADBOptions { - 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; - adbPort?: number; - adbHost?: string; - adbExecTimeout?: number; - remoteAppsCacheLimit?: number; - buildToolsVersion?: string; - allowOfflineDevices?: boolean; - allowDelayAdb?: boolean; - remoteAdbHost?: string; - remoteAdbPort?: number; - clearDeviceLogsOnStart?: boolean; -} - -export interface ADBExecutable { - path: string; - defaultArgs: string[]; -} diff --git a/lib/tools/aab-utils.js b/lib/tools/aab-utils.js index 1a4849f8..1e9a874e 100644 --- a/lib/tools/aab-utils.js +++ b/lib/tools/aab-utils.js @@ -33,23 +33,6 @@ process.on('exit', () => { } }); -/** - * @typedef {Object} ApkCreationOptions - * @property {string} [keystore] Specifies the path to the deployment keystore used - * to sign the APKs. This flag is optional. If you don't include it, - * bundletool attempts to sign your APKs with a debug signing key. - * If the .apk has been already signed and cached then it is not going to be resigned - * unless a different keystore or key alias is used. - * @property {string} [keystorePassword] Specifies your keystore’s password. - * It is mandatory to provide this value if `keystore` one is assigned - * otherwise it is going to be ignored. - * @property {string} [keyAlias] Specifies the alias of the signing key you want to use. - * It is mandatory to provide this value if `keystore` one is assigned - * otherwise it is going to be ignored. - * @property {string} [keyPassword] Specifies the password for the signing key. - * It is mandatory to provide this value if `keystore` one is assigned - * otherwise it is going to be ignored. - */ /** * Builds a universal .apk from the given .aab package. See @@ -58,7 +41,7 @@ process.on('exit', () => { * * @this {import('../adb.js').ADB} * @param {string} aabPath Full path to the source .aab package - * @param {ApkCreationOptions} [opts={}] + * @param {import('./types').ApkCreationOptions} [opts={}] * @returns The path to the resulting universal .apk. The .apk is stored in the internal cache * by default. * @throws {Error} If there was an error while creating the universal .apk @@ -108,7 +91,7 @@ export async function extractUniversalApk (aabPath, opts = {}) { await this.initAapt2(); const args = [ 'build-apks', - '--aapt2', (/** @type {import('@appium/types').StringRecord} */ (this.binaries)).aapt2, + '--aapt2', (/** @type {import('./types').StringRecord} */ (this.binaries)).aapt2, '--bundle', aabPath, '--output', tmpApksPath, ...(keystore ? [ diff --git a/lib/tools/adb-commands.js b/lib/tools/adb-commands.js index 8e4fb3ad..f700609c 100644 --- a/lib/tools/adb-commands.js +++ b/lib/tools/adb-commands.js @@ -130,7 +130,7 @@ export async function initZipAlign () { */ export async function initBundletool () { try { - (/** @type {import('@appium/types').StringRecord} */(this.binaries)).bundletool = + (/** @type {import('./types').StringRecord} */(this.binaries)).bundletool = await fs.which('bundletool.jar'); } catch { throw new Error('bundletool.jar binary is expected to be present in PATH. ' + @@ -231,18 +231,6 @@ export function isValidClass (classString) { return !!matchComponentName(classString); } -/** - * @typedef {Object} ResolveActivityOptions - * @property {boolean} [preferCmd=true] Whether to prefer `cmd` tool usage for - * launchable activity name detection. It might be useful to disable it if - * `cmd package resolve-activity` returns 'android/com.android.internal.app.ResolverActivity', - * which means the app has no default handler set in system settings. - * See https://github.com/appium/appium/issues/17128 for more details. - * This option has no effect if the target Android version is below 24 as there - * the corresponding `cmd` subcommand is not implemented and dumpsys usage is the only - * possible way to detect the launchable activity name. - */ - /** * Fetches the fully qualified name of the launchable activity for the * given package. It is expected the package is already installed on @@ -250,7 +238,7 @@ export function isValidClass (classString) { * * @this {import('../adb.js').ADB} * @param {string} pkg - The target package identifier - * @param {ResolveActivityOptions} opts + * @param {import('./types').ResolveActivityOptions} opts * @return {Promise} Fully qualified name of the launchable activity * @throws {Error} If there was an error while resolving the activity name */ @@ -1182,7 +1170,7 @@ export async function restart () { * Start the logcat process to gather logs. * * @this {import('../adb.js').ADB} - * @param {import('../logcat.js').LogcatOpts} [opts={}] + * @param {import('./types').LogcatOpts} [opts={}] * @throws {Error} If restart fails. */ export async function startLogcat (opts = {}) { @@ -1221,7 +1209,7 @@ export async function stopLogcat () { * The logcat process should be executed by {2link #startLogcat} method. * * @this {import('../adb.js').ADB} - * @return {import('../logcat').LogEntry[]} The collected logcat output. + * @return {import('./types').LogEntry[]} The collected logcat output. * @throws {Error} If logcat process is not running. */ export function getLogcatLogs () { @@ -1231,26 +1219,11 @@ export function getLogcatLogs () { return this.logcat.getLogs(); } -/** - * Listener function, which accepts one argument. - * - * The argument is a log record object with `timestamp`, `level` and `message` properties. - * @callback LogcatListener - * @param {LogcatRecord} record - */ - -/** - * @typedef LogcatRecord - * @property {number} timestamp - * @property {string} level - * @property {string} message - */ - /** * Set the callback for the logcat output event. * * @this {import('../adb.js').ADB} - * @param {LogcatListener} listener - Listener function + * @param {import('./types').LogcatListener} listener - Listener function * @throws {Error} If logcat process is not running. */ export function setLogcatListener (listener) { @@ -1264,8 +1237,9 @@ export function setLogcatListener (listener) { * Removes the previously set callback for the logcat output event. * * @this {import('../adb.js').ADB} - * @param {LogcatListener} listener - The listener function, which has been previously - * passed to `setLogcatListener` + * @param {import('./types').LogcatListener} listener + * The listener function, which has been previously + * passed to `setLogcatListener` * @throws {Error} If logcat process is not running. */ export function removeLogcatListener (listener) { @@ -1535,11 +1509,6 @@ export async function getDeviceProperty (property) { return val; } -/** - * @typedef {Object} SetPropOpts - * @property {boolean} [privileged=true] - Do we run setProp as a privileged command? Default true. - */ - /** * Set the particular property of the device under test. * @@ -1547,7 +1516,7 @@ export async function getDeviceProperty (property) { * @param {string} prop - The name of the property. This name should * be known to _adb shell setprop_ tool. * @param {string} val - The new property value. - * @param {SetPropOpts} [opts={}] + * @param {import('./types').SetPropOpts} [opts={}] * * @throws {error} If _setprop_ utility fails to change property value. */ @@ -1740,30 +1709,13 @@ export async function bugreport (timeout = 120000) { return await this.adbExec(['bugreport'], {timeout}); } -/** - * @typedef {Object} ScreenrecordOptions - * @property {string} [videoSize] - The format is widthxheight. - * The default value is the device's native display resolution (if supported), - * 1280x720 if not. For best results, - * use a size supported by your device's Advanced Video Coding (AVC) encoder. - * For example, "1280x720" - * @property {boolean} [bugReport] - Set it to `true` in order to display additional information on the video overlay, - * such as a timestamp, that is helpful in videos captured to illustrate bugs. - * This option is only supported since API level 27 (Android P). - * @property {string|number} [timeLimit] - The maximum recording time, in seconds. - * The default (and maximum) value is 180 (3 minutes). - * @property {string|number} [bitRate] - The video bit rate for the video, in megabits per second. - * The default value is 4. You can increase the bit rate to improve video quality, - * but doing so results in larger movie files. - */ - /** * Initiate screenrecord utility on the device * * @this {import('../adb.js').ADB} * @param {string} destination - Full path to the writable media file destination * on the device file system. - * @param {ScreenrecordOptions} [options={}] + * @param {import('./types').ScreenrecordOptions} [options={}] * @returns {SubProcess} screenrecord process, which can be then controlled by the client code */ export function screenrecord (destination, options = {}) { @@ -2043,8 +1995,8 @@ export async function setDataState (on, isEmulator = false) { * Could be empty if no ports are opened. * * @this {import('../adb.js').ADB} - * @param {PortFamily} [family='4'] - * @returns {Promise} + * @param {import('./types').PortFamily} [family='4'] + * @returns {Promise} */ export async function listPorts(family = '4') { const sourceProcName = `/proc/net/tcp${family === '6' ? '6' : ''}`; @@ -2062,7 +2014,7 @@ export async function listPorts(family = '4') { log.debug(lines[0]); throw new Error(`Cannot parse the header row of ${sourceProcName} payload`); } - /** @type {PortInfo[]} */ + /** @type {import('./types').PortInfo[]} */ const result = []; // 2: 1002000A:D036 24CE3AD8:01BB 08 00000000:00000000 00:00000000 00000000 10132 0 49104 1 0000000000000000 21 4 20 10 -1 for (const line of lines.slice(1)) { @@ -2080,14 +2032,3 @@ export async function listPorts(family = '4') { }; return result; } - -/** - * @typedef {'4'|'6'} PortFamily - */ - -/** - * @typedef {Object} PortInfo - * @property {number} port The actual port number between 0 and 0xFFFF - * @property {PortFamily} family Either '4' or '6' - * @property {number} state See https://elixir.bootlin.com/linux/v4.14.42/source/include/net/tcp_states.h - */ diff --git a/lib/tools/adb-emu-commands.js b/lib/tools/adb-emu-commands.js index 72e2ff80..5b72c181 100644 --- a/lib/tools/adb-emu-commands.js +++ b/lib/tools/adb-emu-commands.js @@ -7,79 +7,10 @@ import B from 'bluebird'; import path from 'path'; import * as ini from 'ini'; -export const POWER_AC_STATES = Object.freeze({ - POWER_AC_ON: 'on', - POWER_AC_OFF: 'off' -}); -export const GSM_CALL_ACTIONS = Object.freeze({ - GSM_CALL: 'call', - GSM_ACCEPT: 'accept', - GSM_CANCEL: 'cancel', - GSM_HOLD: 'hold' -}); -export const GSM_VOICE_STATES = Object.freeze({ - GSM_VOICE_UNREGISTERED: 'unregistered', - GSM_VOICE_HOME: 'home', - GSM_VOICE_ROAMING: 'roaming', - GSM_VOICE_SEARCHING: 'searching', - GSM_VOICE_DENIED: 'denied', - GSM_VOICE_OFF: 'off', - GSM_VOICE_ON: 'on' -}); -/** @typedef {0|1|2|3|4} GsmSignalStrength */ -export const GSM_SIGNAL_STRENGTHS = Object.freeze([0, 1, 2, 3, 4]); - -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). - EDGE: 'edge', // EDGE/EGPRS (up: 473.6, down: 473.6). - UMTS: 'umts', // UMTS/3G (up: 384.0, down: 384.0). - HSDPA: 'hsdpa', // HSDPA (up: 5760.0, down: 13,980.0). - LTE: 'lte', // LTE (up: 58,000, down: 173,000). - EVDO: 'evdo', // EVDO (up: 75,000, down: 280,000). - FULL: 'full' // No limit, the default (up: 0.0, down: 0.0). -}); - -export const SENSORS = Object.freeze({ - ACCELERATION: 'acceleration', - GYROSCOPE: 'gyroscope', - MAGNETIC_FIELD: 'magnetic-field', - ORIENTATION: 'orientation', - TEMPERATURE: 'temperature', - PROXIMITY: 'proximity', - LIGHT: 'light', - PRESSURE: 'pressure', - HUMIDITY: 'humidity', - MAGNETIC_FIELD_UNCALIBRATED: 'magnetic-field-uncalibrated', - GYROSCOPE_UNCALIBRATED: 'gyroscope-uncalibrated', - HINGE_ANGLE0: 'hinge-angle0', - HINGE_ANGLE1: 'hinge-angle1', - HINGE_ANGLE2: 'hinge-angle2', - HEART_RATE: 'heart-rate', - RGBC_LIGHT: 'rgbc-light', -}); - -/** - * @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 {Object} EmuInfo - * @property {string} name Emulator name, for example `Pixel_XL_API_30` - * @property {string} config Full path to the emulator config .ini file, - * for example `/Users/user/.android/avd/Pixel_XL_API_30.ini` - */ - /** * Retrieves the list of available Android emulators * - * @returns {Promise} + * @returns {Promise} */ async function listEmulators () { let avdsRoot = process.env.ANDROID_AVD_HOME; @@ -109,7 +40,7 @@ async function listEmulators () { * Get configuration paths of all virtual devices * * @param {string} avdsRoot Path to the directory that contains the AVD .ini files - * @returns {Promise} + * @returns {Promise} */ async function getAvdConfigPaths (avdsRoot) { const configs = await fs.glob('*.ini', { @@ -177,7 +108,7 @@ export async function rotate () { * Emulate power state change on the connected emulator. * * @this {import('../adb.js').ADB} - * @param {PowerAcStates} [state='on'] - Either 'on' or 'off'. + * @param {import('./types').PowerAcStates} [state='on'] - Either 'on' or 'off'. */ export async function powerAC (state = 'on') { if (_.values(this.POWER_AC_STATES).indexOf(state) === -1) { @@ -192,7 +123,7 @@ export async function powerAC (state = 'on') { * * @this {import('../adb.js').ADB} * @param {string} sensor - Sensor type declared in SENSORS items. - * @param {Sensors} value - Number to set as the sensor value. + * @param {import('./types').Sensors} value - Number to set as the sensor value. * @throws {TypeError} - If sensor type or sensor value is not defined */ export async function sensorSet (sensor, value) { @@ -254,7 +185,7 @@ export async function sendSMS (phoneNumber, message = '') { * * @this {import('../adb.js').ADB} * @param {string|number} phoneNumber - The phone number of the caller. - * @param {GsmCallActions} action - One of available GSM call actions. + * @param {import('./types').GsmCallActions} action - One of available GSM call actions. * @throws {TypeError} If phone number has invalid format. * @throws {TypeError} If _action_ value is invalid. */ @@ -274,12 +205,12 @@ export async function gsmCall (phoneNumber, action) { * Emulate GSM signal strength change event on the connected emulator. * * @this {import('../adb.js').ADB} - * @param {GsmSignalStrength} [strength=4] - A number in range [0, 4]; + * @param {import('./types').GsmSignalStrength} [strength=4] - A number in range [0, 4]; * @throws {TypeError} If _strength_ value is invalid. */ export async function gsmSignal (strength = 4) { const strengthInt = parseInt(`${strength}`, 10); - if (!this.GSM_SIGNAL_STRENGTHS.includes(strengthInt)) { + if (!_.includes(this.GSM_SIGNAL_STRENGTHS, strengthInt)) { throw new TypeError( `Invalid signal strength param ${strength}. Supported values: ${_.values(this.GSM_SIGNAL_STRENGTHS)}` ); @@ -292,7 +223,7 @@ export async function gsmSignal (strength = 4) { * Emulate GSM voice event on the connected emulator. * * @this {import('../adb.js').ADB} - * @param {GsmVoiceStates} [state='on'] - Either 'on' or 'off'. + * @param {import('./types').GsmVoiceStates} [state='on'] - Either 'on' or 'off'. * @throws {TypeError} If _state_ value is invalid. */ export async function gsmVoice (state = 'on') { @@ -309,7 +240,7 @@ export async function gsmVoice (state = 'on') { * Emulate network speed change event on the connected emulator. * * @this {import('../adb.js').ADB} - * @param {NetworkSpeed} [speed='full'] + * @param {import('./types').NetworkSpeed} [speed='full'] * One of possible NETWORK_SPEED values. * @throws {TypeError} If _speed_ value is invalid. */ @@ -323,18 +254,6 @@ export async function networkSpeed (speed = 'full') { await this.adbExecEmu(['network', 'speed', speed]); } -/** - * @typedef {Object} ExecTelnetOptions - * @property {number} [execTimeout=60000] A timeout used to wait for a server - * reply to the given command - * @property {number} [connTimeout=5000] Console connection timeout in milliseconds - * @property {number} [initTimeout=5000] Telnet console initialization timeout - * in milliseconds (the time between connection happens and the command prompt - * is available) - * @property {number|string} [port] The emulator port number. The method will try to parse it - * from the current device identifier if unset - */ - /** * Executes a command through emulator telnet console interface and returns its output * @@ -342,7 +261,7 @@ export async function networkSpeed (speed = 'full') { * @param {string[]|string} cmd - The actual command to execute. See * https://developer.android.com/studio/run/emulator-console for more details * on available commands - * @param {ExecTelnetOptions} [opts={}] + * @param {import('./types').ExecTelnetOptions} [opts={}] * @returns {Promise} The command output * @throws {Error} If there was an error while connecting to the Telnet console * or if the given command returned non-OK response @@ -429,17 +348,11 @@ export async function execEmuConsoleCommand (cmd, opts = {}) { }); } -/** - * @typedef {Object} EmuVersionInfo - * @property {string} [revision] The actual revision number, for example '30.0.5' - * @property {number} [buildId] The build identifier, for example 6306047 - */ - /** * Retrieves emulator version from the file system * * @this {import('../adb.js').ADB} - * @returns {Promise} If no version info could be parsed then an empty + * @returns {Promise} If no version info could be parsed then an empty * object is returned */ export async function getEmuVersionInfo () { @@ -467,7 +380,7 @@ export async function getEmuVersionInfo () { * @this {import('../adb.js').ADB} * @param {string} avdName Emulator name. Should NOT start with '@' character * @throws {Error} if there was a failure while extracting the properties - * @returns {Promise} The content of emulator image properties file. + * @returns {Promise} The content of emulator image properties file. * Usually this configuration .ini file has the following content: * avd.ini.encoding=UTF-8 * path=/Users/username/.android/avd/Pixel_XL_API_30.avd diff --git a/lib/tools/android-manifest.js b/lib/tools/android-manifest.js index 5397d50c..8e4022d9 100644 --- a/lib/tools/android-manifest.js +++ b/lib/tools/android-manifest.js @@ -7,18 +7,12 @@ import { import { fs, zip, tempDir, util } from '@appium/support'; import path from 'path'; -/** - * @typedef {Object} APKInfo - * @property {string} apkPackage - The name of application package, for example 'com.acme.app'. - * @property {string} [apkActivity] - The name of main application activity. - */ - /** * Extract package and main activity name from application manifest. * * @this {import('../adb.js').ADB} * @param {string} appPath - The full path to application .apk(s) package - * @return {Promise} The parsed application info. + * @return {Promise} The parsed application info. * @throws {error} If there was an error while getting the data from the given * application package. */ @@ -114,10 +108,10 @@ export async function compileManifest (manifest, manifestPackage, targetPackage) '-v', ]; log.debug(`Compiling the manifest using '${util.quote([ - (/** @type {import('@appium/types').StringRecord} */ (this.binaries)).aapt2, + (/** @type {import('./types').StringRecord} */ (this.binaries)).aapt2, ...args ])}'`); - await exec((/** @type {import('@appium/types').StringRecord} */ (this.binaries)).aapt2, args); + await exec((/** @type {import('./types').StringRecord} */ (this.binaries)).aapt2, args); } catch (e) { log.debug('Cannot compile the manifest using aapt2. Defaulting to aapt. ' + `Original error: ${e.stderr || e.message}`); @@ -132,11 +126,11 @@ export async function compileManifest (manifest, manifestPackage, targetPackage) '-f', ]; log.debug(`Compiling the manifest using '${util.quote([ - (/** @type {import('@appium/types').StringRecord} */ (this.binaries)).aapt, + (/** @type {import('./types').StringRecord} */ (this.binaries)).aapt, ...args ])}'`); try { - await exec((/** @type {import('@appium/types').StringRecord} */ (this.binaries)).aapt, args); + await exec((/** @type {import('./types').StringRecord} */ (this.binaries)).aapt, args); } catch (e1) { throw new Error(`Cannot compile the manifest. Original error: ${e1.stderr || e1.message}`); } @@ -168,11 +162,11 @@ export async function insertManifest (manifest, srcApk, dstApk) { await fs.copyFile(srcApk, dstApk); log.debug('Moving manifest'); try { - await exec((/** @type {import('@appium/types').StringRecord} */ (this.binaries)).aapt, [ + await exec((/** @type {import('./types').StringRecord} */ (this.binaries)).aapt, [ 'remove', dstApk, manifestName ]); } catch {} - await exec((/** @type {import('@appium/types').StringRecord} */ (this.binaries)).aapt, [ + await exec((/** @type {import('./types').StringRecord} */ (this.binaries)).aapt, [ 'add', dstApk, manifestName ], {cwd: path.dirname(manifest)}); } catch (e) { diff --git a/lib/tools/apk-signing.js b/lib/tools/apk-signing.js index 967b0fe1..c8c8057f 100644 --- a/lib/tools/apk-signing.js +++ b/lib/tools/apk-signing.js @@ -21,8 +21,7 @@ const DEFAULT_CERT_HASH = { [SHA256]: 'a40da80a59d170caa950cf15c18c454d47a39b26989d8b640ecd745ba71bf5dc' }; const JAVA_PROPS_INIT_ERROR = 'java.lang.Error: Properties init'; -/** @typedef {{output: string, expected: KeystoreHash, keystorePath: string}} SignedAppCacheValue */ -/** @type {LRUCache} */ +/** @type {LRUCache} */ const SIGNED_APPS_CACHE = new LRUCache({ max: 30, }); @@ -193,7 +192,7 @@ export async function sign (appPath) { export async function zipAlignApk (apk) { await this.initZipAlign(); try { - await exec((/** @type {import('@appium/types').StringRecord} */ (this.binaries)).zipalign, ['-c', '4', apk]); + await exec((/** @type {import('./types').StringRecord} */ (this.binaries)).zipalign, ['-c', '4', apk]); log.debug(`${apk}' is already zip-aligned. Doing nothing`); return false; } catch { @@ -210,7 +209,7 @@ export async function zipAlignApk (apk) { await mkdirp(path.dirname(alignedApk)); try { await exec( - (/** @type {import('@appium/types').StringRecord} */ (this.binaries)).zipalign, + (/** @type {import('./types').StringRecord} */ (this.binaries)).zipalign, ['-f', '4', apk, alignedApk] ); await fs.mv(alignedApk, apk, { mkdirp: true }); @@ -223,20 +222,13 @@ export async function zipAlignApk (apk) { } } -/** - * @typedef {Object} CertCheckOptions - * @property {boolean} [requireDefaultCert=true] Whether to require that the destination APK - * is signed with the default Appium certificate or any valid certificate. This option - * only has effect if `useKeystore` property is unset. - */ - /** * Check if the app is already signed with the default Appium certificate. * * @this {import('../adb.js').ADB} * @param {string} appPath - The full path to the local .apk(s) file. * @param {string} pkg - The name of application package. - * @param {CertCheckOptions} [opts={}] - Certificate checking options + * @param {import('./types').CertCheckOptions} [opts={}] - Certificate checking options * @return {Promise} True if given application is already signed. */ export async function checkApkCert (appPath, pkg, opts = {}) { @@ -267,7 +259,9 @@ export async function checkApkCert (appPath, pkg, opts = {}) { const appHash = await fs.hash(appPath); if (SIGNED_APPS_CACHE.has(appHash)) { log.debug(`Using the previously cached signature entry for '${path.basename(appPath)}'`); - const {keystorePath, output, expected} = /** @type {SignedAppCacheValue} */ (SIGNED_APPS_CACHE.get(appHash)); + const {keystorePath, output, expected} = /** @type {import('./types').SignedAppCacheValue} */ ( + SIGNED_APPS_CACHE.get(appHash) + ); if (this.useKeystore && this.keystorePath === keystorePath || !this.useKeystore) { return (!this.useKeystore && !requireDefaultCert) || hashMatches(output, expected); } @@ -319,19 +313,11 @@ export async function checkApkCert (appPath, pkg, opts = {}) { } } -/** - * @typedef {Object} KeystoreHash - * @property {string} [md5] the md5 hash value of the keystore - * @property {string} [sha1] the sha1 hash value of the keystore - * @property {string} [sha256] the sha256 hash value of the keystore - * @property {string} [sha512] the sha512 hash value of the keystore - */ - /** * Retrieve the the hash of the given keystore. * * @this {import('../adb.js').ADB} - * @return {Promise} + * @return {Promise} * @throws {Error} If getting keystore hash fails. */ export async function getKeystoreHash () { diff --git a/lib/tools/apk-utils.js b/lib/tools/apk-utils.js index b6c0ddb1..010fcadc 100644 --- a/lib/tools/apk-utils.js +++ b/lib/tools/apk-utils.js @@ -14,8 +14,7 @@ import * as semver from 'semver'; import os from 'os'; import { LRUCache } from 'lru-cache'; -/** @typedef {'unknown'|'notInstalled'|'newerVersionInstalled'|'sameVersionInstalled'|'olderVersionInstalled'} InstallState */ -/** @type {Record} */ +/** @type {import('./types').StringRecord} */ export const APP_INSTALL_STATE = { UNKNOWN: 'unknown', NOT_INSTALLED: 'notInstalled', @@ -26,18 +25,12 @@ export const APP_INSTALL_STATE = { export const REMOTE_CACHE_ROOT = '/data/local/tmp/appium_cache'; const RESOLVER_ACTIVITY_NAME = 'android/com.android.internal.app.ResolverActivity'; - -/** - * @typedef {Object} IsAppInstalledOptions - * @property {string} [user] - The user id - */ - /** * Check whether the particular package is present on the device under test. * * @this {import('../adb.js').ADB} * @param {string} pkg - The name of the package to check. - * @param {IsAppInstalledOptions} [opts={}] + * @param {import('./types').IsAppInstalledOptions} [opts={}] * @return {Promise} True if the package is installed. */ export async function isAppInstalled (pkg, opts = {}) { @@ -83,19 +76,13 @@ export async function isAppInstalled (pkg, opts = {}) { return isInstalled; } -/** - * @typedef {Object} StartUriOptions - * @property {boolean} [waitForLaunch=true] - if `false` then adb won't wait - * for the started activity to return the control - */ - /** * Start the particular URI on the device under test. * * @this {import('../adb.js').ADB} * @param {string} uri - The name of URI to start. * @param {string?} [pkg=null] - The name of the package to start the URI with. - * @param {StartUriOptions} [opts={}] + * @param {import('./types').StartUriOptions} [opts={}] */ export async function startUri (uri, pkg = null, opts = {}) { const { @@ -126,40 +113,11 @@ export async function startUri (uri, pkg = null, opts = {}) { } } -/** - * @typedef {Object} StartAppOptions - * @property {string} pkg - The name of the application package - * @property {string} [activity] - The name of the main application activity. - * This or action is required in order to be able to launch an app. - * @property {string} [action] - The name of the intent action that will launch the required app. - * This or activity is required in order to be able to launch an app. - * @property {boolean} [retry=true] - If this property is set to `true` - * and the activity name does not start with '.' then the method - * will try to add the missing dot and start the activity once more - * if the first startup try fails. - * @property {boolean} [stopApp=true] - Set it to `true` in order to forcefully - * stop the activity if it is already running. - * @property {string} [waitPkg] - The name of the package to wait to on - * startup (this only makes sense if this name is different from the one, which is set as `pkg`) - * @property {string} [waitActivity] - The name of the activity to wait to on - * startup (this only makes sense if this name is different from the one, which is set as `activity`) - * @property {number} [waitDuration] - The number of milliseconds to wait until the - * `waitActivity` is focused - * @property {string|number} [user] - The number of the user profile to start - * the given activity with. The default OS user profile (usually zero) is used - * when this property is unset - * @property {boolean} [waitForLaunch=true] - if `false` then adb won't wait - * for the started activity to return the control - * @property {string} [category] - * @property {string} [flags] - * @property {string} [optionalIntentArguments] - */ - /** * Start the particular package/activity on the device under test. * * @this {import('../adb.js').ADB} - * @param {StartAppOptions} startAppOptions - Startup options mapping. + * @param {import('./types').StartAppOptions} startAppOptions - Startup options mapping. * @return {Promise} The output of the corresponding adb command. * @throws {Error} If there is an error while executing the activity */ @@ -242,18 +200,11 @@ export async function dumpWindows () { return await this.shell(cmd); } -/** - * @typedef {Object} PackageActivityInfo - * @property {string?} appPackage - The name of application package, - * for example 'com.acme.app'. - * @property {string?} appActivity - The name of main application activity. - */ - /** * Get the name of currently focused package and activity. * * @this {import('../adb.js').ADB} - * @return {Promise} The mapping, where property names are 'appPackage' and 'appActivity'. + * @return {Promise} * @throws {Error} If there is an error while parsing the data. */ export async function getFocusedPackageAndActivity () { @@ -276,11 +227,11 @@ export async function getFocusedPackageAndActivity () { const nullCurrentFocusRe = /^\s*mCurrentFocus=null/m; const currentFocusAppRe = new RegExp('^\\s*mCurrentFocus.+\\{.+\\s([^\\s\\/]+)\\/([^\\s]+)\\b', 'mg'); - /** @type {PackageActivityInfo[]} */ + /** @type {import('./types').PackageActivityInfo[]} */ const focusedAppCandidates = []; - /** @type {PackageActivityInfo[]} */ + /** @type {import('./types').PackageActivityInfo[]} */ const currentFocusAppCandidates = []; - /** @type {[PackageActivityInfo[], RegExp][]} */ + /** @type {[import('./types').PackageActivityInfo[], RegExp][]} */ const pairs = [ [focusedAppCandidates, focusedAppRe], [currentFocusAppCandidates, currentFocusAppRe] @@ -419,22 +370,12 @@ export async function waitForNotActivity (pkg, act, waitMs = 20000) { await this.waitForActivityOrNot(pkg, act, true, waitMs); } -/** - * @typedef {Object} UninstallOptions - * @property {number} [timeout] - The count of milliseconds to wait until the - * app is uninstalled. - * @property {boolean} [keepData] - Set to true in order to keep the - * application data and cache folders after uninstall. - * @property {boolean} [skipInstallCheck] Whether to check if the app is installed prior to - * uninstalling it. By default this is checked. - */ - /** * Uninstall the given package from the device under test. * * @this {import('../adb.js').ADB} * @param {string} pkg - The name of the package to be uninstalled. - * @param {UninstallOptions} [options={}] - The set of uninstall options. + * @param {import('./types').UninstallOptions} [options={}] - The set of uninstall options. * @return {Promise} True if the package was found on the device and * successfully uninstalled. */ @@ -472,7 +413,7 @@ export async function uninstallApk (pkg, options = {}) { * * @this {import('../adb.js').ADB} * @param {string} apkPathOnDevice - The full path to the package on the device file system. - * @param {import('./system-calls.js').ShellExecOptions} [opts={}] Additional exec options. + * @param {import('./types').ShellExecOptions} [opts={}] Additional exec options. * @throws {error} If there was a failure during application install. */ export async function installFromDevicePath (apkPathOnDevice, opts = {}) { @@ -482,18 +423,12 @@ export async function installFromDevicePath (apkPathOnDevice, opts = {}) { } } -/** - * @typedef {Object} CachingOptions - * @property {number} [timeout] - The count of milliseconds to wait until the - * app is uploaded to the remote location. - */ - /** * Caches the given APK at a remote location to speed up further APK deployments. * * @this {import('../adb.js').ADB} * @param {string} apkPath - Full path to the apk on the local FS - * @param {CachingOptions} [options={}] - Caching options + * @param {import('./types').CachingOptions} [options={}] - Caching options * @returns {Promise} - Full path to the cached apk on the remote file system * @throws {Error} if there was a failure while caching the app */ @@ -575,34 +510,12 @@ export async function cacheApk (apkPath, options = {}) { return remotePath; } -/** - * @typedef {Object} InstallOptions - * @property {number} [timeout=60000] - The count of milliseconds to wait until the - * app is installed. - * @property {string} [timeoutCapName=androidInstallTimeout] - The timeout option name - * users can increase the timeout. - * @property {boolean} [allowTestPackages=false] - Set to true in order to allow test - * packages installation. - * @property {boolean} [useSdcard=false] - Set to true to install the app on sdcard - * instead of the device memory. - * @property {boolean} [grantPermissions=false] - Set to true in order to grant all the - * permissions requested in the application's manifest - * automatically after the installation is completed - * under Android 6+. - * @property {boolean} [replace=true] - Set it to false if you don't want - * the application to be upgraded/reinstalled - * if it is already present on the device. - * @property {boolean} [noIncremental=false] - Forcefully disables incremental installs if set to `true`. - * Read https://developer.android.com/preview/features#incremental - * for more details. - */ - /** * Install the package from the local file system. * * @this {import('../adb.js').ADB} * @param {string} appPath - The full path to the local package. - * @param {InstallOptions} [options={}] - The set of installation options. + * @param {import('./types').InstallOptions} [options={}] - The set of installation options. * @throws {Error} If an unexpected error happens during install. */ export async function install (appPath, options = {}) { @@ -710,7 +623,7 @@ export async function install (appPath, options = {}) { * @param {string} appPath - Full path to the application * @param {string?} [pkg=null] - Package identifier. If omitted then the script will * try to extract it on its own - * @returns {Promise} One of `APP_INSTALL_STATE` constants + * @returns {Promise} One of `APP_INSTALL_STATE` constants */ export async function getApplicationInstallState (appPath, pkg = null) { let apkInfo = null; @@ -777,31 +690,6 @@ export async function getApplicationInstallState (appPath, pkg = null) { return this.APP_INSTALL_STATE.OLDER_VERSION_INSTALLED; } -/** - * @typedef {Object} InstallOrUpgradeOptions - * @property {number} [timeout=60000] - The count of milliseconds to wait until the - * app is installed. - * @property {boolean} [allowTestPackages=false] - Set to true in order to allow test - * packages installation. - * @property {boolean} [useSdcard=false] - Set to true to install the app on SDCard - * instead of the device memory. - * @property {boolean} [grantPermissions=false] - Set to true in order to grant all the - * permissions requested in the application's manifest - * automatically after the installation is completed - * under Android 6+. - * @property {boolean} [enforceCurrentBuild=false] - Set to `true` in order to always prefer - * the current build over any installed packages having - * the same identifier - */ - -/** - * @typedef {Object} InstallOrUpgradeResult - * @property {boolean} wasUninstalled - Equals to `true` if the target app has been uninstalled - * before being installed - * @property {InstallState} appState - One of `adb.APP_INSTALL_STATE` states, which reflects - * the state of the application before being installed. - */ - /** * Install the package from the local file system or upgrade it if an older * version of the same package is already installed. @@ -810,9 +698,9 @@ export async function getApplicationInstallState (appPath, pkg = null) { * @param {string} appPath - The full path to the local package. * @param {string?} [pkg=null] - The name of the installed package. The method will * perform faster if it is set. - * @param {InstallOrUpgradeOptions} [options={}] - Set of install options. + * @param {import('./types').InstallOrUpgradeOptions} [options={}] - Set of install options. * @throws {Error} If an unexpected error happens during install. - * @returns {Promise} + * @returns {Promise} */ export async function installOrUpgrade (appPath, pkg = null, options = {}) { if (!pkg) { @@ -887,13 +775,6 @@ export async function installOrUpgrade (appPath, pkg = null, options = {}) { }; } -/** - * @typedef {Object} ApkStrings - * @property {import('@appium/types').StringRecord} apkStrings parsed resource file - * represented as JSON object - * @property {string} [localPath] the path to the extracted file on the local file system - */ - /** * Extract string resources from the given package on local file system. * @@ -904,7 +785,7 @@ export async function installOrUpgrade (appPath, pkg = null, options = {}) { * @param {string?} [outRoot=null] - The name of the destination folder on the local file system to * store the extracted file to. If not provided then the `localPath` property in the returned object * will be undefined. - * @return {Promise} + * @return {Promise} */ export async function extractStringsFromApk ( appPath, @@ -923,13 +804,13 @@ export async function extractStringsFromApk ( await this.initAapt(); configMarker = await formatConfigMarker(async () => { - const {stdout} = await exec((/** @type {import('@appium/types').StringRecord} */ (this.binaries)).aapt, [ + const {stdout} = await exec((/** @type {import('./types').StringRecord} */ (this.binaries)).aapt, [ 'd', 'configurations', appPath, ]); return _.uniq(stdout.split(os.EOL)); }, language, '(default)'); - const {stdout} = await exec((/** @type {import('@appium/types').StringRecord} */ (this.binaries)).aapt, [ + const {stdout} = await exec((/** @type {import('./types').StringRecord} */ (this.binaries)).aapt, [ 'd', '--values', 'resources', appPath, ]); apkStrings = parseAaptStrings(stdout, configMarker); @@ -940,14 +821,14 @@ export async function extractStringsFromApk ( await this.initAapt2(); configMarker = await formatConfigMarker(async () => { - const {stdout} = await exec((/** @type {import('@appium/types').StringRecord} */ (this.binaries)).aapt2, [ + const {stdout} = await exec((/** @type {import('./types').StringRecord} */ (this.binaries)).aapt2, [ 'd', 'configurations', appPath, ]); return _.uniq(stdout.split(os.EOL)); }, language, ''); try { - const {stdout} = await exec((/** @type {import('@appium/types').StringRecord} */ (this.binaries)).aapt2, [ + const {stdout} = await exec((/** @type {import('./types').StringRecord} */ (this.binaries)).aapt2, [ 'd', 'resources', appPath, ]); apkStrings = parseAapt2Strings(stdout, configMarker); @@ -1071,21 +952,13 @@ export async function ensureCurrentLocale (language, country, script) { })); } -/** - * @typedef {Object} AppInfo - * @property {string} name - Package name, for example 'com.acme.app'. - * @property {number?} [versionCode] - Version code. - * @property {string?} [versionName] - Version name, for example '1.0'. - * @property {boolean?} [isInstalled] - true if the app is installed on the device under test. - */ - /** * Get the package info from local apk file. * * @this {import('../adb.js').ADB} * @param {string} appPath - The full path to existing .apk(s) package on the local * file system. - * @return {Promise} The parsed application information. + * @return {Promise} The parsed application information. */ export async function getApkInfo (appPath) { if (!await fs.exists(appPath)) { @@ -1114,7 +987,7 @@ export async function getApkInfo (appPath) { * * @this {import('../adb.js').ADB} * @param {string} pkg - The name of the installed package. - * @return {Promise} The parsed application information. + * @return {Promise} The parsed application information. */ export async function getPackageInfo (pkg) { log.debug(`Getting package info for '${pkg}'`); diff --git a/lib/tools/apks-utils.js b/lib/tools/apks-utils.js index 109b1f57..ef5abbcf 100644 --- a/lib/tools/apks-utils.js +++ b/lib/tools/apks-utils.js @@ -99,7 +99,7 @@ async function extractFromApks (apks, dstPath) { export async function execBundletool (args, errorMsg) { await this.initBundletool(); args = [ - '-jar', (/** @type {import('@appium/types').StringRecord} */ (this.binaries)).bundletool, + '-jar', (/** @type {import('./types').StringRecord} */ (this.binaries)).bundletool, ...args ]; const env = process.env; @@ -149,29 +149,12 @@ export async function getDeviceSpec (specLocation) { return specLocation; } -/** - * @typedef {Object} InstallMultipleApksOptions - * @property {?number|string} [timeout=20000] - The number of milliseconds to wait until - * the installation is completed - * @property {string} [timeoutCapName=androidInstallTimeout] - The timeout option name - * users can increase the timeout. - * @property {boolean} [allowTestPackages=false] - Set to true in order to allow test - * packages installation. - * @property {boolean} [useSdcard=false] - Set to true to install the app on sdcard - * instead of the device memory. - * @property {boolean} [grantPermissions=false] - Set to true in order to grant all the - * permissions requested in the application's manifest automatically after the installation - * is completed under Android 6+. - * @property {boolean} [partialInstall=false] - Install apks partially. It is used for 'install-multiple'. - * https://android.stackexchange.com/questions/111064/what-is-a-partial-application-install-via-adb - */ - /** * Installs the given apks into the device under test * * @this {import('../adb.js').ADB} * @param {Array} apkPathsToInstall - The full paths to install apks - * @param {InstallMultipleApksOptions} [options={}] - Installation options + * @param {import('./types').InstallMultipleApksOptions} [options={}] - Installation options */ export async function installMultipleApks (apkPathsToInstall, options = {}) { const installArgs = buildInstallArgs(await this.getApiLevel(), options); @@ -182,25 +165,12 @@ export async function installMultipleApks (apkPathsToInstall, options = {}) { }); } -/** - * @typedef {Object} InstallApksOptions - * @property {number|string} [timeout=120000] - The number of milliseconds to wait until - * the installation is completed - * @property {string} [timeoutCapName=androidInstallTimeout] - The timeout option name - * users can increase the timeout. - * @property {boolean} [allowTestPackages=false] - Set to true in order to allow test - * packages installation. - * @property {boolean} [grantPermissions=false] - Set to true in order to grant all the - * permissions requested in the application's manifest automatically after the installation - * is completed under Android 6+. - */ - /** * Installs the given .apks package into the device under test * * @this {import('../adb.js').ADB} * @param {string} apks - The full path to the .apks file - * @param {InstallApksOptions} [options={}] - Installation options + * @param {import('./types').InstallApksOptions} [options={}] - Installation options * @throws {Error} If the .apks bundle cannot be installed */ export async function installApks (apks, options = {}) { diff --git a/lib/tools/emu-constants.ts b/lib/tools/emu-constants.ts new file mode 100644 index 00000000..12f1b9c4 --- /dev/null +++ b/lib/tools/emu-constants.ts @@ -0,0 +1,50 @@ + +export const POWER_AC_STATES = { + POWER_AC_ON: 'on', + POWER_AC_OFF: 'off' +} as const; +export const GSM_CALL_ACTIONS = { + GSM_CALL: 'call', + GSM_ACCEPT: 'accept', + GSM_CANCEL: 'cancel', + GSM_HOLD: 'hold' +} as const; +export const GSM_VOICE_STATES = { + GSM_VOICE_UNREGISTERED: 'unregistered', + GSM_VOICE_HOME: 'home', + GSM_VOICE_ROAMING: 'roaming', + GSM_VOICE_SEARCHING: 'searching', + GSM_VOICE_DENIED: 'denied', + GSM_VOICE_OFF: 'off', + GSM_VOICE_ON: 'on' +} as const; +export const GSM_SIGNAL_STRENGTHS = [0, 1, 2, 3, 4] as const; +export const NETWORK_SPEED = { + 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). + EDGE: 'edge', // EDGE/EGPRS (up: 473.6, down: 473.6). + UMTS: 'umts', // UMTS/3G (up: 384.0, down: 384.0). + HSDPA: 'hsdpa', // HSDPA (up: 5760.0, down: 13,980.0). + LTE: 'lte', // LTE (up: 58,000, down: 173,000). + EVDO: 'evdo', // EVDO (up: 75,000, down: 280,000). + FULL: 'full' // No limit, the default (up: 0.0, down: 0.0). +} as const; +export const SENSORS = { + ACCELERATION: 'acceleration', + GYROSCOPE: 'gyroscope', + MAGNETIC_FIELD: 'magnetic-field', + ORIENTATION: 'orientation', + TEMPERATURE: 'temperature', + PROXIMITY: 'proximity', + LIGHT: 'light', + PRESSURE: 'pressure', + HUMIDITY: 'humidity', + MAGNETIC_FIELD_UNCALIBRATED: 'magnetic-field-uncalibrated', + GYROSCOPE_UNCALIBRATED: 'gyroscope-uncalibrated', + HINGE_ANGLE0: 'hinge-angle0', + HINGE_ANGLE1: 'hinge-angle1', + HINGE_ANGLE2: 'hinge-angle2', + HEART_RATE: 'heart-rate', + RGBC_LIGHT: 'rgbc-light', +} as const; diff --git a/lib/tools/keyboard-commands.js b/lib/tools/keyboard-commands.js index 581fe720..0d2e0053 100644 --- a/lib/tools/keyboard-commands.js +++ b/lib/tools/keyboard-commands.js @@ -33,19 +33,13 @@ export async function hideKeyboard (timeoutMs = 1000) { } catch {} } throw new Error(`The software keyboard cannot be hidden`); -}; - -/** - * @typedef {Object} KeyboardState - * @property {boolean} isKeyboardShown - Whether soft keyboard is currently visible. - * @property {boolean} canCloseKeyboard - Whether the keyboard can be closed. - */ +} /** * Retrieve the state of the software keyboard on the device under test. * * @this {import('../adb.js').ADB} - * @return {Promise} The keyboard state. + * @return {Promise} The keyboard state. */ export async function isSoftKeyboardPresent () { try { @@ -59,4 +53,4 @@ export async function isSoftKeyboardPresent () { } catch (e) { throw new Error(`Error finding softkeyboard. Original error: ${e.message}`); } -}; +} diff --git a/lib/tools/system-calls.js b/lib/tools/system-calls.js index a1f76e7a..6e726d65 100644 --- a/lib/tools/system-calls.js +++ b/lib/tools/system-calls.js @@ -83,8 +83,8 @@ export const getBinaryNameForOS = _.memoize(_getBinaryNameForOS); * local file system. */ export async function getBinaryFromSdkRoot (binaryName) { - if ((/** @type {import('@appium/types').StringRecord} */ (this.binaries))[binaryName]) { - return (/** @type {import('@appium/types').StringRecord} */ (this.binaries))[binaryName]; + if ((/** @type {import('./types').StringRecord} */ (this.binaries))[binaryName]) { + return (/** @type {import('./types').StringRecord} */ (this.binaries))[binaryName]; } const fullBinaryName = this.getBinaryNameForOS(binaryName); const binaryLocs = getSdkBinaryLocationCandidates( @@ -122,7 +122,7 @@ export async function getBinaryFromSdkRoot (binaryName) { `installed at '${this.sdkRoot}'?`); } log.info(`Using '${fullBinaryName}' from '${binaryLoc}'`); - (/** @type {import('@appium/types').StringRecord} */ (this.binaries))[binaryName] = binaryLoc; + (/** @type {import('./types').StringRecord} */ (this.binaries))[binaryName] = binaryLoc; return binaryLoc; } @@ -177,15 +177,15 @@ export async function getAndroidBinaryPath (binaryName) { * @throws {Error} If lookup tool returns non-zero return code. */ export async function getBinaryFromPath (binaryName) { - if ((/** @type {import('@appium/types').StringRecord} */ (this.binaries))[binaryName]) { - return (/** @type {import('@appium/types').StringRecord} */ (this.binaries))[binaryName]; + if ((/** @type {import('./types').StringRecord} */ (this.binaries))[binaryName]) { + return (/** @type {import('./types').StringRecord} */ (this.binaries))[binaryName]; } const fullBinaryName = this.getBinaryNameForOS(binaryName); try { const binaryLoc = await fs.which(fullBinaryName); log.info(`Using '${fullBinaryName}' from '${binaryLoc}'`); - (/** @type {import('@appium/types').StringRecord} */ (this.binaries))[binaryName] = binaryLoc; + (/** @type {import('./types').StringRecord} */ (this.binaries))[binaryName] = binaryLoc; return binaryLoc; } catch { throw new Error(`Could not find '${fullBinaryName}' in PATH. Please set the ANDROID_HOME ` + @@ -193,35 +193,12 @@ export async function getBinaryFromPath (binaryName) { } } -/** - * @typedef {Object} ConnectedDevicesOptions - * @property {boolean} [verbose] - Whether to get long output, which includes extra properties in each device. - * Akin to running `adb devices -l`. - */ - -/** - * @typedef {Object} Device - * @property {string} udid - The device udid. - * @property {string} state - Current device state, as it is visible in - * _adb devices -l_ output. - * @property {number} [port] - */ - -/** - * @typedef {Device} VerboseDevice Additional properties returned when `verbose` is true. - * @property {string} product - The product codename of the device, such as "razor". - * @property {string} model - The model name of the device, such as "Nexus_7". - * @property {string} device - The device codename, such as "flow". - * @property {?string} usb - Represents the USB port the device is connected to, such as "1-1". - * @property {?string} transport_id - The Transport ID for the device, such as "1". - */ - /** * Retrieve the list of devices visible to adb. * * @this {import('../adb.js').ADB} - * @param {ConnectedDevicesOptions} [opts={}] - Additional options mapping. - * @return {Promise} The list of devices or an empty list if + * @param {import('./types').ConnectedDevicesOptions} [opts={}] - Additional options mapping. + * @return {Promise} The list of devices or an empty list if * no devices are connected. * @throws {Error} If there was an error while listing devices. */ @@ -284,7 +261,7 @@ export async function getConnectedDevices (opts = {}) { * @this {import('../adb.js').ADB} * @param {number} timeoutMs - The maximum number of milliseconds to get at least * one list item. - * @return {Promise} The list of connected devices. + * @return {Promise} The list of connected devices. * @throws {Error} If no connected devices can be detected within the given timeout. */ export async function getDevicesWithRetry (timeoutMs = 20000) { @@ -419,45 +396,16 @@ export async function adbExecEmu (cmd) { let isExecLocked = false; -/** @typedef {'stdout'|'full'} ExecOutputFormat */ /** @type {{STDOUT: 'stdout', FULL: 'full'}} */ export const EXEC_OUTPUT_FORMAT = Object.freeze({ STDOUT: 'stdout', FULL: 'full', }); -/** - * @typedef {Object} ExecResult - * @property {string} stdout The stdout received from exec - * @property {string} stderr The stderr received from exec - */ - -/** - * @typedef {Object} SpecialAdbExecOptions - * @property {boolean} [exclusive] - */ - -/** - * @typedef {Object} ShellExecOptions - * @property {string} [timeoutCapName] - the name of the corresponding Appium's timeout capability - * (used in the error messages). - * @property {number} [timeout] - command execution timeout. - * @property {boolean} [privileged=false] - Whether to run the given command as root. - * @property {ExecOutputFormat} [outputFormat='stdout'] - Whether response should include full exec output or just stdout. - * Potential values are full or stdout. - * - * All other properties are the same as for `exec` call from {@link https://github.com/appium/node-teen_process} - * module - */ - -/** - * @typedef {{outputFormat: 'full'}} TFullOutputOption - */ - /** * Execute the given adb command. * - * @template {import('teen_process').TeenProcessExecOptions & ShellExecOptions & SpecialAdbExecOptions} TExecOpts + * @template {import('teen_process').TeenProcessExecOptions & import('./types').ShellExecOptions & import('./types').SpecialAdbExecOptions} TExecOpts * @this {import('../adb.js').ADB} * @param {string|string[]} cmd - The array of rest command line parameters * or a single string parameter. @@ -470,7 +418,7 @@ export const EXEC_OUTPUT_FORMAT = Object.freeze({ * You can set the `outputFormat` param to `stdout` to receive just the stdout * output (default) or `full` to receive the stdout and stderr response from a * command with a zero exit code - * @return {Promise} + * @return {Promise} * Command's stdout or an object containing stdout and stderr. * @throws {Error} If the command returned non-zero exit code. */ @@ -552,11 +500,11 @@ export async function adbExec (cmd, opts) { * Execute the given command using _adb shell_ prefix. * * @this {import('../adb.js').ADB} - * @template {ShellExecOptions} TShellExecOpts + * @template {import('./types').ShellExecOptions} TShellExecOpts * @param {string|string[]} cmd - The array of rest command line parameters or a single * string parameter. * @param {TShellExecOpts} [opts] - Additional options mapping. - * @return {Promise} + * @return {Promise} * Command's stdout. * @throws {Error} If the command returned non-zero exit code. */ @@ -650,8 +598,8 @@ export function getPortFromEmulatorString (emStr) { * Retrieve the list of currently connected emulators. * * @this {import('../adb.js').ADB} - * @param {ConnectedDevicesOptions} [opts={}] - Additional options mapping. - * @return {Promise} The list of connected devices. + * @param {import('./types').ConnectedDevicesOptions} [opts={}] - Additional options mapping. + * @return {Promise} The list of connected devices. */ export async function getConnectedEmulators (opts = {}) { log.debug('Getting connected emulators'); @@ -703,7 +651,7 @@ export function setDeviceId (deviceId) { * Set the the current device object. * * @this {import('../adb.js').ADB} - * @param {Device} deviceObj - The device object to be set. + * @param {import('./types').Device} deviceObj - The device object to be set. */ export function setDevice (deviceObj) { const deviceId = deviceObj.udid; @@ -722,7 +670,7 @@ export function setDevice (deviceObj) { * * @this {import('../adb.js').ADB} * @param {string} avdName - Emulator name. - * @return {Promise} Currently running emulator or _null_. + * @return {Promise} Currently running emulator or _null_. */ export async function getRunningAVD (avdName) { log.debug(`Trying to find '${avdName}' emulator`); @@ -758,12 +706,12 @@ export async function getRunningAVD (avdName) { * @param {number} [timeoutMs=20000] - The maximum number of milliseconds * to wait until at least one running AVD object * is detected. - * @return {Promise} Currently running emulator or _null_. + * @return {Promise} Currently running emulator or _null_. * @throws {Error} If no device has been detected within the timeout. */ export async function getRunningAVDWithRetry (avdName, timeoutMs = 20000) { try { - return /** @type {Device|null} */ (await waitForCondition(async () => { + return /** @type {import('./types').Device|null} */ (await waitForCondition(async () => { try { return await this.getRunningAVD(avdName.replace('@', '')); } catch (e) { @@ -850,24 +798,12 @@ export async function killEmulator (avdName = null, timeout = 60000) { return true; } -/** - * @typedef {Object} AvdLaunchOptions - * @property {string|string[]} [args] Additional emulator command line arguments - * @property {Object} [env] Additional emulator environment variables - * @property {string} [language] Emulator system language - * @property {string} [country] Emulator system country - * @property {number} [launchTimeout=60000] Emulator startup timeout in milliseconds - * @property {number} [readyTimeout=60000] The maximum period of time to wait until Emulator - * is ready for usage in milliseconds - * @property {number} [retryTimes=1] The maximum number of startup retries - */ - /** * Start an emulator with given parameters and wait until it is fully started. * * @this {import('../adb.js').ADB} * @param {string} avdName - The name of an existing emulator. - * @param {AvdLaunchOptions} [opts={}] + * @param {import('./types').AvdLaunchOptions} [opts={}] * @returns {Promise} Emulator subprocess instance * @throws {Error} If the emulator fails to start within the given timeout. */ @@ -949,29 +885,11 @@ export async function launchAVD (avdName, opts = {}) { return proc; } -/** - * @typedef {Object} BinaryVersion - * @property {string} version - The ADB binary version number - * @property {number} build - The ADB binary build number - */ - -/** - * @typedef {Object} BridgeVersion - * @property {string} version - The Android Debug Bridge version number - */ - -/** - * @typedef {Object} Version - * @property {BinaryVersion?} binary This version number might not be - * be present for older ADB releases. - * @property {BridgeVersion} bridge - */ - /** * Get the adb version. The result of this method is cached. * * @this {import('../adb.js').ADB} - * @return {Promise} + * @return {Promise} * @throws {Error} If it is not possible to parse adb binary version. */ export const getVersion = _.memoize(async function getVersion () { @@ -1138,18 +1056,12 @@ export async function reboot (retries = DEFAULT_ADB_REBOOT_RETRIES) { }); } -/** - * @typedef {Object} RootResult - * @property {boolean} isSuccessful True if the call to root/unroot was successful - * @property {boolean} wasAlreadyRooted True if the device was already rooted - */ - /** * Switch adb server root privileges. * * @this {import('../adb.js').ADB} * @param {boolean} isElevated - Should we elevate to to root or unroot? (default true) - * @return {Promise} + * @return {Promise} */ export async function changeUserPrivileges (isElevated) { const cmd = isElevated ? 'root' : 'unroot'; @@ -1208,7 +1120,7 @@ export async function changeUserPrivileges (isElevated) { * Switch adb server to root mode * * @this {import('../adb.js').ADB} - * @return {Promise} + * @return {Promise} */ export async function root () { return await this.changeUserPrivileges(true); @@ -1218,7 +1130,7 @@ export async function root () { * Switch adb server to non-root mode. * * @this {import('../adb.js').ADB} - * @return {Promise} + * @return {Promise} */ export async function unroot () { return await this.changeUserPrivileges(false); diff --git a/lib/tools/types.ts b/lib/tools/types.ts new file mode 100644 index 00000000..f5a25720 --- /dev/null +++ b/lib/tools/types.ts @@ -0,0 +1,767 @@ +import * as emuConstants from './emu-constants'; + +export type StringRecord = Record; + +export interface ApkCreationOptions { + /** + * Specifies the path to the deployment keystore used + * to sign the APKs. This flag is optional. If you don't include it, + * bundletool attempts to sign your APKs with a debug signing key. + * If the .apk has been already signed and cached then it is not going to be resigned + * unless a different keystore or key alias is used. + */ + keystore?: string; + /** + * Specifies your keystore’s password. + * It is mandatory to provide this value if `keystore` one is assigned + * otherwise it is going to be ignored. + */ + keystorePassword?: string; + /** + * Specifies the alias of the signing key you want to use. + * It is mandatory to provide this value if `keystore` one is assigned + * otherwise it is going to be ignored. + */ + keyAlias?: string; + /** + * Specifies the password for the signing key. + * It is mandatory to provide this value if `keystore` one is assigned + * otherwise it is going to be ignored. + */ + keyPassword?: string; +} + +export interface LogcatOpts { + /** + * The log print format, where is one of: + * brief process tag thread raw time threadtime long + * `threadtime` is the default value. + */ + format?: string; + /** + * Series of `[:priority]` + * where `` is a log component tag (or `*` for all) and priority is: + * V Verbose + * D Debug + * I Info + * W Warn + * E Error + * F Fatal + * S Silent (supress all output) + * + * `'*'` means `'*:d'` and `` by itself means `:v` + * + * If not specified on the commandline, filterspec is set from `ANDROID_LOG_TAGS`. + * If no filterspec is found, filter defaults to `'*:I'` + */ + filterSpecs?: string[]; +} + +export interface ResolveActivityOptions { + /** + * Whether to prefer `cmd` tool usage for + * launchable activity name detection. It might be useful to disable it if + * `cmd package resolve-activity` returns 'android/com.android.internal.app.ResolverActivity', + * which means the app has no default handler set in system settings. + * See https://github.com/appium/appium/issues/17128 for more details. + * This option has no effect if the target Android version is below 24 as there + * the corresponding `cmd` subcommand is not implemented and dumpsys usage is the only + * possible way to detect the launchable activity name. + * `true` by default. + */ + preferCmd?: boolean; +} + +export interface LogEntry { + timestamp: number; + level: 'ALL'; + message: string; +} + +/** + * Listener function, which accepts one argument. + */ +export type LogcatListener = (logEntry: LogEntry) => any + +export interface SetPropOpts { + /** + * Do we run setProp as a privileged command? Default true. + */ + privileged?: boolean; +} + +export interface ScreenrecordOptions { + /** + * The format is widthxheight. + * The default value is the device's native display resolution (if supported), + * 1280x720 if not. For best results, + * use a size supported by your device's Advanced Video Coding (AVC) encoder. + * For example, "1280x720" + */ + videoSize?: string; + /** + * Set it to `true` in order to display additional information on the video overlay, + * such as a timestamp, that is helpful in videos captured to illustrate bugs. + * This option is only supported since API level 27 (Android P) + */ + bugReport?: boolean; + /** + * The maximum recording time, in seconds. + * The default (and maximum) value is 180 (3 minutes). + */ + timeLimit?: string | number; + /** + * The video bit rate for the video, in megabits per second. + * The default value is 4. You can increase the bit rate to improve video quality, + * but doing so results in larger movie files. + */ + bitRate?: string | number; +} + +export type PortFamily = '4' | '6'; + +export interface PortInfo { + /** + * The actual port number between 0 and 0xFFFF + */ + port: number; + family: PortFamily; + /** + * See https://elixir.bootlin.com/linux/v4.14.42/source/include/net/tcp_states.h + */ + state: number; +} + +export interface APKInfo { + /** + * The name of application package, for example 'com.acme.app'. + */ + apkPackage: string; + /** + * The name of main application activity. + */ + apkActivity?: string; +} + +export interface ExecTelnetOptions { + /** + * A timeout used to wait for a server + * reply to the given command. 60000ms by default. + */ + execTimeout?: number; + /** + * Console connection timeout in milliseconds. + * 5000ms by default. + */ + connTimeout?: number; + /** + * Telnet console initialization timeout + * in milliseconds (the time between connection happens and the command prompt + * is available). + * 5000ms by default. + */ + initTimeout?: number; + /** + * The emulator port number. The script will try to parse it + * from the current device identifier if unset + */ + port?: number | string; +} + +export interface EmuVersionInfo { + /** + * The actual revision number, for example '30.0.5' + */ + revision?: string; + /** + * The build identifier, for example 6306047 + */ + buildId?: number; +} + +export type GsmSignalStrength = typeof emuConstants.GSM_SIGNAL_STRENGTHS[number]; + +export interface EmuInfo { + /** + * Emulator name, for example `Pixel_XL_API_30` + */ + name: string; + /** + * Full path to the emulator config .ini file, + * for example `/Users/user/.android/avd/Pixel_XL_API_30.ini` + */ + config: string; +} + +export interface KeystoreHash { + /** + * the md5 hash value of the keystore + */ + md5?: string; + /** + * the sha1 hash value of the keystore + */ + sha1?: string; + /** + * the sha256 hash value of the keystore + */ + sha256?: string; + /** + * the sha512 hash value of the keystore + */ + sha512?: string; +} + +export type SignedAppCacheValue = { output: string; expected: KeystoreHash; keystorePath: string; }; + +export interface CertCheckOptions { + /** + * Whether to require that the destination APK + * is signed with the default Appium certificate or any valid certificate. This option + * only has effect if `useKeystore` property is unset. + * `true` by default. + */ + requireDefaultCert?: boolean; +} + +export interface InstallMultipleApksOptions { + /** + * The number of milliseconds to wait until + * the installation is completed. 20000ms by default. + */ + timeout?: number | string; + /** + * The option name + * used to increase the install timeout. + * `androidInstallTimeout` by default + */ + timeoutCapName?: string; + /** + * Set to true in order to allow test + * packages installation. `false` by default. + */ + allowTestPackages?: boolean; + /** + * Set to true to install the app on sdcard + * instead of the device memory. `false` by default. + */ + useSdcard?: boolean; + /** + * Set to true in order to grant all the + * permissions requested in the application's manifest automatically after the installation + * is completed under Android 6+. + * `false` by default. + */ + grantPermissions?: boolean; + /** + * Install apks partially. It is used for 'install-multiple'. + * https://android.stackexchange.com/questions/111064/what-is-a-partial-application-install-via-adb. + * `false` by default. + */ + partialInstall?: boolean; +} + +export interface InstallApksOptions { + /** + * The number of milliseconds to wait until + * the installation is completed. + * 120000ms by default. + */ + timeout?: number | string; + /** + * The option name + * used to increase the install timeout. + * `androidInstallTimeout` by default + */ + timeoutCapName?: string; + /** + * Set to true in order to allow test + * packages installation. `false` by default. + */ + allowTestPackages?: boolean; + /** + * Set to true in order to grant all the + * permissions requested in the application's manifest automatically after the installation + * is completed under Android 6+. + * `false` by default. + */ + grantPermissions?: boolean; +} + +export interface KeyboardState { + /** + * Whether soft keyboard is currently visible. + */ + isKeyboardShown: boolean; + /** + * Whether the keyboard can be closed. + */ + canCloseKeyboard: boolean; +} + +export type InstallState = 'unknown' | 'notInstalled' | 'newerVersionInstalled' | 'sameVersionInstalled' | 'olderVersionInstalled'; + +export interface IsAppInstalledOptions { + /** + * The user id + */ + user?: string; +} + +export interface StartUriOptions { + /** + * If `false` then adb won't wait + * for the started activity to return the control. + * `true` by default. + */ + waitForLaunch?: boolean; +} + +export interface StartAppOptions { + /** + * The name of the application package + */ + pkg: string; + /** + * The name of the main application activity. + * This or action is required in order to be able to launch an app. + */ + activity?: string; + /** + * The name of the intent action that will launch the required app. + * This or activity is required in order to be able to launch an app. + */ + action?: string; + /** + * If this property is set to `true` + * and the activity name does not start with '.' then the method + * will try to add the missing dot and start the activity once more + * if the first startup try fails. + * `true` by default. + */ + retry?: boolean; + /** + * Set it to `true` in order to forcefully + * stop the activity if it is already running. + * `true` by default. + */ + stopApp?: boolean; + /** + * The name of the package to wait to on + * startup (this only makes sense if this name is + * different from the one, which is set as `pkg`) + */ + waitPkg?: string; + /** + * The name of the activity to wait to on + * startup (this only makes sense if this name is different + * from the one, which is set as `activity`) + */ + waitActivity?: string; + /** + * The number of milliseconds to wait until the + * `waitActivity` is focused + */ + waitDuration?: number; + /** + * The number of the user profile to start + * the given activity with. The default OS user profile (usually zero) is used + * when this property is unset + */ + user?: string | number; + /** + * If `false` then adb won't wait + * for the started activity to return the control. + * `true` by default. + */ + waitForLaunch?: boolean; + category?: string; + flags?: string; + optionalIntentArguments?: string; +} + +export interface PackageActivityInfo { + /** + * The name of application package, + * for example 'com.acme.app'. + */ + appPackage?: string | null; + /** + * The name of main application activity. + */ + appActivity?: string | null; +} + +export interface UninstallOptions { + /** + * The count of milliseconds to wait until the + * app is uninstalled. + */ + timeout?: number; + /** + * Set to true in order to keep the + * application data and cache folders after uninstall. + */ + keepData?: boolean; + /** + * Whether to check if the app is installed prior to + * uninstalling it. By default this is checked. + */ + skipInstallCheck?: boolean; +} + +export interface CachingOptions { + /** + * The count of milliseconds to wait until the + * app is uploaded to the remote location. + */ + timeout?: number; +} + +export interface InstallOptions { + /** + * The count of milliseconds to wait until the + * app is installed. + * 20000ms by default. + */ + timeout?: number; + /** + * The option name + * used to increase the timeout. + * `androidInstallTimeout` by default. + */ + timeoutCapName?: string; + /** + * Set to true in order to allow test + * packages installation. + * `false` by default. + */ + allowTestPackages?: boolean; + /** + * Set to true to install the app on sdcard + * instead of the device memory. + * `false` by default. + */ + useSdcard?: boolean; + /** + * Set to true in order to grant all the + * permissions requested in the application's manifest + * automatically after the installation is completed + * under Android 6+. + * `false` by default. + */ + grantPermissions?: boolean; + /** + * Set it to false if you don't want + * the application to be upgraded/reinstalled + * if it is already present on the device. + * `true` by default. + */ + replace?: boolean; + /** + * Forcefully disables incremental installs if set to `true`. + * Read https://developer.android.com/preview/features#incremental + * for more details. + * `false` by default. + */ + noIncremental?: boolean; +} + +export interface InstallOrUpgradeOptions { + /** + * The count of milliseconds to wait until the + * app is installed. + * 60000ms by default. + */ + timeout?: number; + /** + * Set to true in order to allow test + * packages installation. + * `false` by default. + */ + allowTestPackages?: boolean; + /** + * Set to true to install the app on SDCard + * instead of the device memory. + * `false` by default. + */ + useSdcard?: boolean; + /** + * Set to true in order to grant all the + * permissions requested in the application's manifest + * automatically after the installation is completed + * under Android 6+. + * `false` by default. + */ + grantPermissions?: boolean; + /** + * Set to `true` in order to always prefer + * the current build over any installed packages having + * the same identifier. + * `false` by default. + */ + enforceCurrentBuild?: boolean; +} + +export interface InstallOrUpgradeResult { + /** + * Equals to `true` if the target app has been uninstalled + * before being installed + */ + wasUninstalled: boolean; + /** + * One of `adb.APP_INSTALL_STATE` states, which reflects + * the state of the application before being installed. + */ + appState: InstallState; +} + +export interface ApkStrings { + /** + * Parsed resource file + * represented as JSON object. + */ + apkStrings: StringRecord; + /** + * The path to the extracted file on the local file system + */ + localPath?: string; +} + +export interface AppInfo { + /** + * Package name, for example 'com.acme.app'. + */ + name: string; + /** + * Version code. + */ + versionCode?: number; + /** + * Version name, for example '1.0'. + */ + versionName?: string; + /** + * true if the app is installed on the device under test. + */ + isInstalled?: boolean; +} + +export interface ConnectedDevicesOptions { + /** + * Whether to get long output, which includes extra properties in each device. + * Akin to running `adb devices -l`. + */ + verbose?: boolean; +} + +export interface Device { + /** + * The device udid. + */ + udid: string; + /** + * Current device state, as it is visible in + * _adb devices -l_ output. + */ + state: string; + port?: number; +} + +export interface VerboseDevice extends Device { + /** + * The product codename of the device, such as "razor". + */ + product: string; + /** + * The model name of the device, such as "Nexus_7". + */ + model: string; + /** + * The device codename, such as "flow". + */ + device: string; + /** + * Represents the USB port the device is connected to, such as "1-1". + */ + usb?: string; + /** + * The Transport ID for the device, such as "1". + */ + transport_id?: string; +} + +export type ExecOutputFormat = 'stdout' | 'full'; + +export interface ExecResult { + /** + * The stdout received from exec + */ + stdout: string; + /** + * The stderr received from exec + */ + stderr: string; +} + +export interface SpecialAdbExecOptions { + exclusive?: boolean; +} + +export interface ShellExecOptions { + /** + * The name of the corresponding Appium's timeout capability + * (used in the error messages). + */ + timeoutCapName?: string; + /** + * command execution timeout + */ + timeout?: number; + /** + * Whether to run the given command as root. + * `false` by default. + */ + privileged?: boolean; + /** + * Whether response should include full exec output or just stdout. + * Potential values are full or stdout. + * + * All other properties are the same as for `exec` call from {@link https://github.com/appium/node-teen_process} + * module. + * `stdout` by default. + */ + outputFormat?: ExecOutputFormat; +} + +export type TFullOutputOption = { outputFormat: 'full'; }; + +export interface AvdLaunchOptions { + /** + * Additional emulator command line arguments + */ + args?: string | string[]; + /** + * Additional emulator environment variables + */ + env?: Record; + /** + * Emulator system language + */ + language?: string; + /** + * Emulator system country + */ + country?: string; + /** + * Emulator startup timeout in milliseconds. + * 60000ms by default. + */ + launchTimeout?: number; + /** + * The maximum period of time to wait until Emulator + * is ready for usage in milliseconds. + * 60000ms by default. + */ + readyTimeout?: number; + /** + * The maximum number of startup retries. + * `1` by default. + */ + retryTimes?: number; +} + +export interface BinaryVersion { + /** + * The ADB binary version number + */ + version: string; + /** + * The ADB binary build number + */ + build: number; +} + +export interface BridgeVersion { + /** + * The Android Debug Bridge version number + */ + version: string; +} + +export interface Version { + /** + * This version number might not be + * be present for older ADB releases. + */ + binary?: BinaryVersion; + bridge: BridgeVersion; +} + +export interface RootResult { + /** + * True if the call to root/unroot was successful + */ + isSuccessful: boolean; + /** + * True if the device was already rooted + */ + wasAlreadyRooted: boolean; +} + +export type Sensors = typeof emuConstants.SENSORS[keyof typeof emuConstants.SENSORS]; +export type NetworkSpeed = typeof emuConstants.NETWORK_SPEED[keyof typeof emuConstants.NETWORK_SPEED]; +export type GsmVoiceStates = typeof emuConstants.GSM_VOICE_STATES[keyof typeof emuConstants.GSM_VOICE_STATES]; +export type GsmCallActions = typeof emuConstants.GSM_CALL_ACTIONS[keyof typeof emuConstants.GSM_CALL_ACTIONS]; +export type PowerAcStates = typeof emuConstants.POWER_AC_STATES[keyof typeof emuConstants.POWER_AC_STATES]; + +export interface PlatformInfo { + /** + * The platform name, for example `android-24` + * or `null` if it cannot be found + */ + platform?: string | null; + /** + * Full path to the platform SDK folder + * or `null` if it cannot be found + */ + platformPath?: string | null; +} + +export interface LaunchableActivity { + name: string; + label?: string; + icon?: string; +} + +export interface ApkManifest { + /** + * Package name, for example 'io.appium.android.apis' + */ + name: string; + versionCode: number; + versionName?: string; + platformBuildVersionName?: string; + platformBuildVersionCode?: number; + compileSdkVersion: number; + compileSdkVersionCodename?: string; + minSdkVersion: number; + targetSdkVersion?: number; + /** + * List of requested permissions + */ + usesPermissions: string[]; + launchableActivity: LaunchableActivity; + /** + * List of supported locales + */ + locales: string[]; + /** + * List of supported architectures. Could be empty for older apps. + */ + architectures: string[]; + /** + * List of supported display densities + */ + densities: number[]; +} diff --git a/lib/types.ts b/lib/types.ts new file mode 100644 index 00000000..0f75e1cc --- /dev/null +++ b/lib/types.ts @@ -0,0 +1,35 @@ +import type {Logcat} from './logcat'; +import type {StringRecord} from './tools/types'; + +export interface ADBOptions { + 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; + adbPort?: number; + adbHost?: string; + adbExecTimeout?: number; + remoteAppsCacheLimit?: number; + buildToolsVersion?: string; + allowOfflineDevices?: boolean; + allowDelayAdb?: boolean; + remoteAdbHost?: string; + remoteAdbPort?: number; + clearDeviceLogsOnStart?: boolean; +} + +export interface ADBExecutable { + path: string; + defaultArgs: string[]; +} diff --git a/package.json b/package.json index de209dba..56e034de 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "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",