From 1acdbc62dd0d91027f803548e8312da69114ea53 Mon Sep 17 00:00:00 2001 From: itsspriyansh Date: Wed, 17 Jul 2024 19:42:44 +0530 Subject: [PATCH 1/4] feat: disconnect a device --- src/commands/android/constants.ts | 6 +- .../android/subcommands/disconnect/index.ts | 78 +++++++++++++++++++ src/commands/android/subcommands/index.ts | 9 ++- 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 src/commands/android/subcommands/disconnect/index.ts diff --git a/src/commands/android/constants.ts b/src/commands/android/constants.ts index 0fe09f4..5a0dfda 100644 --- a/src/commands/android/constants.ts +++ b/src/commands/android/constants.ts @@ -1,6 +1,6 @@ import inquirer from 'inquirer'; -import path from 'path'; import os from 'os'; +import path from 'path'; import {AvailableOptions, AvailableSubcommands, SdkBinary} from './interfaces'; @@ -40,6 +40,10 @@ export const AVAILABLE_SUBCOMMANDS: AvailableSubcommands = { description: 'Connect a real device wirelessly' } ] + }, + disconnect: { + description: 'Disconnect a real device or emulator', + options: [] } }; diff --git a/src/commands/android/subcommands/disconnect/index.ts b/src/commands/android/subcommands/disconnect/index.ts new file mode 100644 index 0000000..7b6c7ec --- /dev/null +++ b/src/commands/android/subcommands/disconnect/index.ts @@ -0,0 +1,78 @@ +import colors from 'ansi-colors'; +import ADB from 'appium-adb'; +import inquirer from 'inquirer'; + +import Logger from '../../../../logger'; +import {killEmulatorWithoutWait} from '../../adb'; +import {Options, Platform} from '../../interfaces'; +import {getBinaryLocation} from '../../utils/common'; +import {execBinarySync} from '../../utils/sdk'; +import {showConnectedRealDevices, showRunningAVDs} from '../common'; + +export async function disconnect(options: Options, sdkRoot: string, platform: Platform) { + try { + const adbLocation = getBinaryLocation(sdkRoot, platform, 'adb', true); + const adb = await ADB.createADB({allowOfflineDevices: true}); + const devices = await adb.getConnectedDevices(); + + if (devices.length === 0) { + Logger.log(`${colors.yellow('No device found running.')}`); + + return true; + } + + const devicesList = devices.map((device) => device.udid); + + // Here, options.s represent the device id to disconnect. + // If the provided device id is not found then prompt the user to select the device. + if (options.s && typeof options.s === 'string') { + if (!devicesList.includes(options.s)) { + Logger.log(`${colors.yellow('Device with the provided ID was not found.')}\n`); + options.s = ''; + } + } else if (options.s === true) { + // If the --s flag is present without a value then assign it an empty string + // to follow the default flow. + options.s = ''; + } + + await showConnectedRealDevices(); + await showRunningAVDs(); + + if (!options.s) { + const deviceAnswer = await inquirer.prompt({ + type: 'list', + name: 'device', + message: 'Select the device to disconnect:', + choices: devicesList + }); + options.s = deviceAnswer.device; + + Logger.log(); + } + + if ((options.s as string).includes('emulator')) { + killEmulatorWithoutWait(sdkRoot, platform, options.s as string); + Logger.log(colors.green('Successfully shut down the AVD.')); + + return true; + } + + const disconnectionStatus = execBinarySync(adbLocation, 'adb', platform, `disconnect ${options.s}`); + if (disconnectionStatus?.includes('disconnected')) { + Logger.log(colors.green('Successfully disconnected the device.')); + + return true; + } else { + Logger.log(`${colors.red('Failed to disconnect the device.')} Please try again.`); + } + + return false; + } catch (err) { + Logger.log(colors.red('Error occured while disconnecting a device.')); + console.error(err); + + return false; + } +} + diff --git a/src/commands/android/subcommands/index.ts b/src/commands/android/subcommands/index.ts index 91fa629..4015f0d 100644 --- a/src/commands/android/subcommands/index.ts +++ b/src/commands/android/subcommands/index.ts @@ -2,11 +2,12 @@ import colors from 'ansi-colors'; import * as dotenv from 'dotenv'; import path from 'path'; -import {checkJavaInstallation, getSdkRootFromEnv} from '../utils/common'; -import {connect} from './connect'; -import {getPlatformName} from '../../../utils'; import Logger from '../../../logger'; +import {getPlatformName} from '../../../utils'; import {Options, Platform} from '../interfaces'; +import {checkJavaInstallation, getSdkRootFromEnv} from '../utils/common'; +import {connect} from './connect'; +import {disconnect} from './disconnect'; export class AndroidSubcommand { sdkRoot: string; @@ -56,6 +57,8 @@ export class AndroidSubcommand { async executeSubcommand(): Promise { if (this.subcommand === 'connect') { return await connect(this.options, this.sdkRoot, this.platform); + } else if (this.subcommand === 'disconnect') { + return await disconnect(this.options, this.sdkRoot, this.platform); } return false; From 8cc8f88275a070b59637435352cc1ce440b9f7fa Mon Sep 17 00:00:00 2001 From: itsspriyansh Date: Fri, 19 Jul 2024 20:30:50 +0530 Subject: [PATCH 2/4] minor refactor --- .../android/subcommands/disconnect/index.ts | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/commands/android/subcommands/disconnect/index.ts b/src/commands/android/subcommands/disconnect/index.ts index 7b6c7ec..528896f 100644 --- a/src/commands/android/subcommands/disconnect/index.ts +++ b/src/commands/android/subcommands/disconnect/index.ts @@ -3,6 +3,7 @@ import ADB from 'appium-adb'; import inquirer from 'inquirer'; import Logger from '../../../../logger'; +import {symbols} from '../../../../utils'; import {killEmulatorWithoutWait} from '../../adb'; import {Options, Platform} from '../../interfaces'; import {getBinaryLocation} from '../../utils/common'; @@ -12,6 +13,14 @@ import {showConnectedRealDevices, showRunningAVDs} from '../common'; export async function disconnect(options: Options, sdkRoot: string, platform: Platform) { try { const adbLocation = getBinaryLocation(sdkRoot, platform, 'adb', true); + if (adbLocation === '') { + Logger.log(` ${colors.red(symbols().fail)} ${colors.cyan('adb')} binary not found.\n`); + Logger.log(`Run: ${colors.cyan('npx @nightwatch/mobile-helper android --standalone')} to setup missing requirements.`); + Logger.log(`(Remove the ${colors.gray('--standalone')} flag from the above command if setting up for testing.)\n`); + + return false; + } + const adb = await ADB.createADB({allowOfflineDevices: true}); const devices = await adb.getConnectedDevices(); @@ -23,42 +32,42 @@ export async function disconnect(options: Options, sdkRoot: string, platform: Pl const devicesList = devices.map((device) => device.udid); - // Here, options.s represent the device id to disconnect. + // Here, options.deviceId represent the device id to disconnect. // If the provided device id is not found then prompt the user to select the device. - if (options.s && typeof options.s === 'string') { - if (!devicesList.includes(options.s)) { + if (options.deviceId && typeof options.deviceId === 'string') { + if (!devicesList.includes(options.deviceId)) { Logger.log(`${colors.yellow('Device with the provided ID was not found.')}\n`); - options.s = ''; + options.deviceId = ''; } - } else if (options.s === true) { - // If the --s flag is present without a value then assign it an empty string + } else if (options.deviceId === true) { + // If the --deviceId flag is present without a value then assign it an empty string // to follow the default flow. - options.s = ''; + options.deviceId = ''; } await showConnectedRealDevices(); await showRunningAVDs(); - if (!options.s) { + if (!options.deviceId) { const deviceAnswer = await inquirer.prompt({ type: 'list', name: 'device', message: 'Select the device to disconnect:', choices: devicesList }); - options.s = deviceAnswer.device; + options.deviceId = deviceAnswer.device; Logger.log(); } - if ((options.s as string).includes('emulator')) { - killEmulatorWithoutWait(sdkRoot, platform, options.s as string); + if ((options.deviceId as string).includes('emulator')) { + killEmulatorWithoutWait(sdkRoot, platform, options.deviceId as string); Logger.log(colors.green('Successfully shut down the AVD.')); return true; } - const disconnectionStatus = execBinarySync(adbLocation, 'adb', platform, `disconnect ${options.s}`); + const disconnectionStatus = execBinarySync(adbLocation, 'adb', platform, `disconnect ${options.deviceId}`); if (disconnectionStatus?.includes('disconnected')) { Logger.log(colors.green('Successfully disconnected the device.')); From 7a94c31e844a027c256e58b243bb171878fb2994 Mon Sep 17 00:00:00 2001 From: itsspriyansh Date: Thu, 25 Jul 2024 14:36:31 +0530 Subject: [PATCH 3/4] disconnect all --- .../android/subcommands/disconnect/index.ts | 55 ++++++++++++++++--- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/src/commands/android/subcommands/disconnect/index.ts b/src/commands/android/subcommands/disconnect/index.ts index 528896f..0b67e6a 100644 --- a/src/commands/android/subcommands/disconnect/index.ts +++ b/src/commands/android/subcommands/disconnect/index.ts @@ -4,11 +4,10 @@ import inquirer from 'inquirer'; import Logger from '../../../../logger'; import {symbols} from '../../../../utils'; -import {killEmulatorWithoutWait} from '../../adb'; import {Options, Platform} from '../../interfaces'; import {getBinaryLocation} from '../../utils/common'; import {execBinarySync} from '../../utils/sdk'; -import {showConnectedRealDevices, showRunningAVDs} from '../common'; +import {showConnectedRealDevices, showConnectedEmulators} from '../common'; export async function disconnect(options: Options, sdkRoot: string, platform: Platform) { try { @@ -30,12 +29,12 @@ export async function disconnect(options: Options, sdkRoot: string, platform: Pl return true; } - const devicesList = devices.map((device) => device.udid); + const deviceIdsList = devices.map((device) => device.udid); // Here, options.deviceId represent the device id to disconnect. // If the provided device id is not found then prompt the user to select the device. if (options.deviceId && typeof options.deviceId === 'string') { - if (!devicesList.includes(options.deviceId)) { + if (!deviceIdsList.includes(options.deviceId)) { Logger.log(`${colors.yellow('Device with the provided ID was not found.')}\n`); options.deviceId = ''; } @@ -46,23 +45,61 @@ export async function disconnect(options: Options, sdkRoot: string, platform: Pl } await showConnectedRealDevices(); - await showRunningAVDs(); + await showConnectedEmulators(); if (!options.deviceId) { const deviceAnswer = await inquirer.prompt({ type: 'list', name: 'device', message: 'Select the device to disconnect:', - choices: devicesList + choices: [...deviceIdsList, 'Disconnect all'] }); options.deviceId = deviceAnswer.device; Logger.log(); } - if ((options.deviceId as string).includes('emulator')) { - killEmulatorWithoutWait(sdkRoot, platform, options.deviceId as string); - Logger.log(colors.green('Successfully shut down the AVD.')); + if ((options.deviceId as string).includes('emulator') || options.deviceId === 'Disconnect all') { + if (options.deviceId === 'Disconnect all') { + // kill adb server to disconnect all wirelessly connected real devices + adb.killServer(); + const realDevices = deviceIdsList.filter(deviceId => !deviceId.includes('emulator')); + if (realDevices.length) { + Logger.log(colors.green('Successfully disconnected all real devices.\n')); + } + } + + const avdmanagerLocation = getBinaryLocation(sdkRoot, platform, 'avdmanager', true); + if (avdmanagerLocation === '') { + Logger.log(` ${colors.red(symbols().fail)} ${colors.cyan('avdmanager')} binary not found.\n`); + Logger.log(`Run: ${colors.cyan('npx @nightwatch/mobile-helper android --standalone')} to setup missing requirements.`); + Logger.log(`(Remove the ${colors.gray('--standalone')} flag from the above command if setting up for testing.)\n`); + + return false; + } + const stdout = execBinarySync(avdmanagerLocation, 'avdmanager', platform, 'list avd -c'); + const installedAvds = stdout?.split('\n').filter((avd) => avd !== ''); + + installedAvds?.forEach(avdName => { + adb.getRunningAVDWithRetry(avdName, 1000).then(runningAvd => { + if (runningAvd) { + if (options.deviceId !== 'Disconnect all' && runningAvd.udid !== options.deviceId) { + // If user has selected Disconnect all option then shut down all running avds. + // If not, then we will return until we encounter the selected avd. + return; + } + adb.killEmulator(avdName).then(avdShutDown => { + if (avdShutDown) { + Logger.log(`${colors.green('Successfully shut down: ')} ${runningAvd.udid}`); + } else { + Logger.log(`${colors.red('Failed shut down:')} ${runningAvd.udid}`); + } + }); + } + }).catch(err => { + options.err = err; + }); + }); return true; } From 2c9f88f48332adf32bfb30c8ee173687355e8b3d Mon Sep 17 00:00:00 2001 From: itsspriyansh Date: Sun, 25 Aug 2024 11:52:24 +0530 Subject: [PATCH 4/4] refactors --- src/commands/android/constants.ts | 10 +++++ .../android/subcommands/disconnect/index.ts | 43 ++++++++++++------- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/commands/android/constants.ts b/src/commands/android/constants.ts index d65a4c4..87ee655 100644 --- a/src/commands/android/constants.ts +++ b/src/commands/android/constants.ts @@ -42,6 +42,16 @@ export const AVAILABLE_SUBCOMMANDS: AvailableSubcommands = { } ] }, + disconnect: { + description: 'Disconnect a real device or emulator', + cliConfigs: [{ + name: 'deviceId', + alias: ['s'], + description: 'Id of the device to disconnect', + usageHelp: 'device_id' + }], + flags: [] + }, list: { description: 'List connected devices or installed AVDs', flags: [{ diff --git a/src/commands/android/subcommands/disconnect/index.ts b/src/commands/android/subcommands/disconnect/index.ts index 0b67e6a..7342975 100644 --- a/src/commands/android/subcommands/disconnect/index.ts +++ b/src/commands/android/subcommands/disconnect/index.ts @@ -3,19 +3,25 @@ import ADB from 'appium-adb'; import inquirer from 'inquirer'; import Logger from '../../../../logger'; -import {symbols} from '../../../../utils'; import {Options, Platform} from '../../interfaces'; import {getBinaryLocation} from '../../utils/common'; import {execBinarySync} from '../../utils/sdk'; -import {showConnectedRealDevices, showConnectedEmulators} from '../common'; +import {showConnectedRealDevices, showConnectedEmulators, verifyOptions, showMissingBinaryHelp} from '../common'; export async function disconnect(options: Options, sdkRoot: string, platform: Platform) { + const optionsVerified = verifyOptions('disconnect', options); + if (!optionsVerified) { + return false; + } + + return await disconnectDevice(options, sdkRoot, platform); +} + +async function disconnectDevice(options: Options, sdkRoot: string, platform: Platform) { try { const adbLocation = getBinaryLocation(sdkRoot, platform, 'adb', true); if (adbLocation === '') { - Logger.log(` ${colors.red(symbols().fail)} ${colors.cyan('adb')} binary not found.\n`); - Logger.log(`Run: ${colors.cyan('npx @nightwatch/mobile-helper android --standalone')} to setup missing requirements.`); - Logger.log(`(Remove the ${colors.gray('--standalone')} flag from the above command if setting up for testing.)\n`); + showMissingBinaryHelp('adb'); return false; } @@ -35,7 +41,7 @@ export async function disconnect(options: Options, sdkRoot: string, platform: Pl // If the provided device id is not found then prompt the user to select the device. if (options.deviceId && typeof options.deviceId === 'string') { if (!deviceIdsList.includes(options.deviceId)) { - Logger.log(`${colors.yellow('Device with the provided ID was not found.')}\n`); + Logger.log(`${colors.yellow('Device with the provided id was not found.')}\n`); options.deviceId = ''; } } else if (options.deviceId === true) { @@ -62,25 +68,28 @@ export async function disconnect(options: Options, sdkRoot: string, platform: Pl if ((options.deviceId as string).includes('emulator') || options.deviceId === 'Disconnect all') { if (options.deviceId === 'Disconnect all') { // kill adb server to disconnect all wirelessly connected real devices - adb.killServer(); const realDevices = deviceIdsList.filter(deviceId => !deviceId.includes('emulator')); if (realDevices.length) { + adb.killServer(); Logger.log(colors.green('Successfully disconnected all real devices.\n')); } } const avdmanagerLocation = getBinaryLocation(sdkRoot, platform, 'avdmanager', true); if (avdmanagerLocation === '') { - Logger.log(` ${colors.red(symbols().fail)} ${colors.cyan('avdmanager')} binary not found.\n`); - Logger.log(`Run: ${colors.cyan('npx @nightwatch/mobile-helper android --standalone')} to setup missing requirements.`); - Logger.log(`(Remove the ${colors.gray('--standalone')} flag from the above command if setting up for testing.)\n`); + showMissingBinaryHelp('avdmanager'); return false; } const stdout = execBinarySync(avdmanagerLocation, 'avdmanager', platform, 'list avd -c'); - const installedAvds = stdout?.split('\n').filter((avd) => avd !== ''); + if (stdout === null) { + Logger.log(`${colors.red('Something went wrong when trying to shut down AVD.')} Please try again.`); + + return false; + } + const installedAvds = stdout.split('\n').filter((avd) => avd !== ''); - installedAvds?.forEach(avdName => { + installedAvds.forEach(avdName => { adb.getRunningAVDWithRetry(avdName, 1000).then(runningAvd => { if (runningAvd) { if (options.deviceId !== 'Disconnect all' && runningAvd.udid !== options.deviceId) { @@ -96,8 +105,10 @@ export async function disconnect(options: Options, sdkRoot: string, platform: Pl } }); } - }).catch(err => { - options.err = err; + }).catch(_err => { + // Error is caught to prevent unhandled rejection, but not used. + // This error is not revelant to users. + void _err; }); }); @@ -106,11 +117,11 @@ export async function disconnect(options: Options, sdkRoot: string, platform: Pl const disconnectionStatus = execBinarySync(adbLocation, 'adb', platform, `disconnect ${options.deviceId}`); if (disconnectionStatus?.includes('disconnected')) { - Logger.log(colors.green('Successfully disconnected the device.')); + Logger.log(`${colors.green('Successfully disconnected: ')} ${options.deviceId}\n`); return true; } else { - Logger.log(`${colors.red('Failed to disconnect the device.')} Please try again.`); + Logger.log(`${colors.red('Failed to disconnect the device.')} Please try again.\n`); } return false;