diff --git a/src/commands/android/constants.ts b/src/commands/android/constants.ts index f041984..b3e0150 100644 --- a/src/commands/android/constants.ts +++ b/src/commands/android/constants.ts @@ -19,6 +19,10 @@ export const AVAILABLE_OPTIONS: AvailableOptions = { browsers: { alias: ['browser', 'b'], description: 'Browsers to setup on Android emulator. Available args: "chrome", "firefox", "both", "none"' + }, + appium: { + alias: [], + description: 'Make sure the final setup works with Appium out-of-the-box.' } }; @@ -44,17 +48,17 @@ export const SETUP_CONFIG_QUES: inquirer.QuestionCollection = [ { type: 'list', name: 'mode', - message: 'Where do you want to run the tests?', + message: 'Select target device(s):', choices: [ - {name: 'On real Android device', value: 'real'}, - {name: 'On an Android Emulator', value: 'emulator'}, + {name: 'Real Android Device', value: 'real'}, + {name: 'Android Emulator', value: 'emulator'}, {name: 'Both', value: 'both'} ] }, { type: 'list', name: 'browsers', - message: '[Emulator] Which browser(s) should we set up on the Emulator?', + message: '[Emulator] Select browser(s) to set up on Emulator:', choices: [ {name: 'Google Chrome', value: 'chrome'}, {name: 'Mozilla Firefox', value: 'firefox'}, diff --git a/src/commands/android/index.ts b/src/commands/android/index.ts index 9d4e0d5..f72674d 100644 --- a/src/commands/android/index.ts +++ b/src/commands/android/index.ts @@ -19,25 +19,31 @@ import { downloadFirefoxAndroid, downloadWithProgressBar, getAllAvailableOptions, getBinaryLocation, getBinaryNameForOS, getFirefoxApkName, getLatestVersion } from './utils/common'; -import {downloadAndSetupAndroidSdk, execBinarySync, getDefaultAndroidSdkRoot, installPackagesUsingSdkManager} from './utils/sdk'; +import { + downloadAndSetupAndroidSdk, downloadSdkBuildTools, execBinarySync, + getBuildToolsAvailableVersions, getDefaultAndroidSdkRoot, installPackagesUsingSdkManager +} from './utils/sdk'; import DOWNLOADS from './downloads.json'; export class AndroidSetup { sdkRoot: string; + javaHome: string; options: Options; rootDir: string; platform: Platform; otherInfo: OtherInfo; - constructor(options: Options, rootDir = process.cwd()) { + constructor(options: Options = {}, rootDir = process.cwd()) { this.sdkRoot = ''; + this.javaHome = ''; this.options = options; this.rootDir = rootDir; this.platform = getPlatformName(); this.otherInfo = { - androidHomeInGlobalEnv: false + androidHomeInGlobalEnv: false, + javaHomeInGlobalEnv: false }; } @@ -56,14 +62,52 @@ export class AndroidSetup { return false; } - let result = true; + this.loadEnvFromDotEnv(); + + let javaHomeFound: boolean | null = false; + if (this.options.appium) { + javaHomeFound = this.isJavaHomeEnvSet(); + + if (!javaHomeFound) { + this.javaHomeNotFoundInstructions(); + + if (javaHomeFound === false) { + Logger.log(`${colors.red( + 'ERROR:' + )} JAVA_HOME env variable could not be set in a .env file. Please set the JAVA_HOME env variable as instructed above.`); + + this.envSetHelp(); + + return false; + } + + // env can be set in dotenv file (javaHomeFound=null). + process.env.JAVA_HOME = await this.getJavaHomeFromUser(); + } + + this.javaHome = process.env.JAVA_HOME || ''; + } const sdkRootEnv = this.getSdkRootFromEnv(); - this.sdkRoot = sdkRootEnv || await this.getSdkRootFromUser(); - const originalAndroidHome = process.env.ANDROID_HOME; + if (this.options.appium && !sdkRootEnv && this.otherInfo.androidHomeInGlobalEnv) { + // ANDROID_HOME is set to an invalid path in system env. We can get around this for mobile-web + // since ANDROID_HOME is not a mandatory requirement there and Nightwatch would complain when it + // is required, but for Appium to work properly, it should be set to correct path in sys env or .env. + Logger.log(`${colors.red('ERROR:')} For Appium to work properly, ${colors.cyan( + 'ANDROID_HOME' + )} env variable must be set to a valid path in your system environment variables.`); + + this.envSetHelp(); + + return false; + } + + this.sdkRoot = sdkRootEnv || await this.getSdkRootFromUser(); process.env.ANDROID_HOME = this.sdkRoot; + let result = true; + const setupConfigs: SetupConfigs = await this.getSetupConfigs(this.options); Logger.log(); @@ -94,10 +138,8 @@ export class AndroidSetup { Logger.log(`${colors.bold('Note:')} Please make sure you have required browsers installed on your real-device before running tests.\n`); } - process.env.ANDROID_HOME = originalAndroidHome; - - if (!sdkRootEnv) { - this.sdkRootEnvSetInstructions(); + if (!sdkRootEnv || (this.options.appium && !javaHomeFound)) { + this.envSetInstructions(sdkRootEnv); } return { @@ -162,7 +204,7 @@ export class AndroidSetup { return true; } catch { - Logger.log(`${colors.red('Error:')} Java Development Kit is required to work with Android SDKs. Download from here:`); + Logger.log(`${colors.red('Error:')} Java Development Kit v9 or above is required to work with Android SDKs. Download from here:`); Logger.log(colors.cyan(' https://www.oracle.com/java/technologies/downloads/'), '\n'); Logger.log(`Make sure Java is installed by running ${colors.green('java -version')} command and then re-run this tool.\n`); @@ -171,12 +213,119 @@ export class AndroidSetup { } } - getSdkRootFromEnv(): string { - Logger.log('Checking the value of ANDROID_HOME environment variable...'); - + loadEnvFromDotEnv(): void { this.otherInfo.androidHomeInGlobalEnv = 'ANDROID_HOME' in process.env; + if (this.options.appium) { + this.otherInfo.javaHomeInGlobalEnv = 'JAVA_HOME' in process.env; + } + dotenv.config({path: path.join(this.rootDir, '.env')}); + } + + isJavaHomeEnvSet(): boolean | null { + Logger.log('Checking the value of JAVA_HOME environment variable...'); + + const javaHome = process.env.JAVA_HOME; + const fromDotEnv = this.otherInfo.javaHomeInGlobalEnv ? '' : ' (taken from .env)'; + + if (javaHome !== undefined && fs.existsSync(javaHome)) { + Logger.log(` ${colors.green(symbols().ok)} JAVA_HOME is set to '${javaHome}'${fromDotEnv}`); + + const javaHomeBin = path.resolve(javaHome, 'bin'); + if (fs.existsSync(javaHomeBin)) { + Logger.log(` ${colors.green(symbols().ok)} 'bin' subfolder exists under '${javaHome}'\n`); + + return true; + } + + Logger.log(` ${colors.red(symbols().fail)} 'bin' subfolder does not exist under '${javaHome}'. Is ${javaHome} set to a proper value?\n`); + if (this.otherInfo.javaHomeInGlobalEnv) { + // we cannot set it ourselves + return false; + } + + // We can set the env in dotenv file. + return null; + } + + if (javaHome === undefined) { + Logger.log(` ${colors.red(symbols().fail)} JAVA_HOME env variable is NOT set!\n`); + } else { + Logger.log(` ${colors.red(symbols().fail)} JAVA_HOME is set to '${javaHome}'${fromDotEnv} but this is NOT a valid path!\n`); + + if (this.otherInfo.javaHomeInGlobalEnv) { + // we cannot set it ourselves + return false; + } + } + + // we can set the env in dotenv file. + return null; + } + + javaHomeNotFoundInstructions(): void { + Logger.log(`${colors.red('NOTE:')} For Appium to work properly, ${colors.cyan( + 'JAVA_HOME' + )} env variable must be set to the root folder path of your local JDK installation.`); + + let expectedPath; + + if (this.platform === 'windows') { + expectedPath = 'C:\\Program Files\\Java\\jdk1.8.0_111'; + } else if (this.platform === 'mac') { + expectedPath = '/Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home'; + } else { + expectedPath = '/usr/lib/jvm/java-8-oracle'; + } + + Logger.log(`${colors.green('Hint:')} On a ${this.platform} system, the JDK installation path should be something similar to '${expectedPath}'.\n`); + } + + async getJavaHomeFromUser(): Promise { + let javaHome; + + if (this.platform === 'mac') { + try { + const stdout = execSync('/usr/libexec/java_home', { + stdio: 'pipe' + }); + + javaHome = stdout.toString(); + + Logger.log(`Auto-detected JAVA_HOME to be: ${colors.green(javaHome)}`); + // eslint-disable-next-line + } catch {} + } + + const answers: {javaHome: string} = await prompt([ + { + type: 'input', + name: 'javaHome', + message: 'Enter the path to the root folder of your local JDK installation:', + validate: (input) => { + if (!fs.existsSync(input)) { + return 'Entered path does not exist'; + } + + if (!fs.existsSync(path.resolve(input, 'bin'))) { + return `'bin' subfolder does not exist under '${input}'`; + } + + return true; + } + } + ], {javaHome}); + Logger.log(); + + const envPath = path.join(this.rootDir, '.env'); + fs.appendFileSync(envPath, `\nJAVA_HOME=${answers.javaHome}`); + + return answers.javaHome; + } + + getSdkRootFromEnv(): string { + Logger.log('Checking the value of ANDROID_HOME environment variable...'); const androidHome = process.env.ANDROID_HOME; const fromDotEnv = this.otherInfo.androidHomeInGlobalEnv ? '' : ' (taken from .env)'; @@ -228,7 +377,7 @@ export class AndroidSetup { // this is important if global ANDROID_HOME env is set to '', in which case we // should not save the user supplied value to .env. const envPath = path.join(this.rootDir, '.env'); - fs.appendFileSync(envPath, `\nANDROID_HOME=${sdkRoot}\n`); + fs.appendFileSync(envPath, `\nANDROID_HOME=${sdkRoot}`); } return sdkRoot; @@ -406,6 +555,23 @@ export class AndroidSetup { const missingBinaries = this.checkBinariesPresent(requiredBinaries); missingRequirements.push(...missingBinaries); + // check for build-tools + if (this.options.appium) { + const buildToolsPath = path.join(this.sdkRoot, 'build-tools'); + const availableVersions = getBuildToolsAvailableVersions(buildToolsPath); + if (availableVersions.length > 0) { + Logger.log( + ` ${colors.green(symbols().ok)} ${colors.cyan('Android Build Tools')} present at '${buildToolsPath}'.`, + `Available versions: ${colors.cyan(availableVersions.join(', '))}\n` + ); + } else { + Logger.log( + ` ${colors.red(symbols().fail)} ${colors.cyan('Android Build Tools')} not present at '${buildToolsPath}'\n` + ); + missingRequirements.push('build-tools'); + } + } + // check for platforms subdirectory (required by emulator) if (requiredBinaries.includes('emulator')) { const platormsPath = path.join(this.sdkRoot, 'platforms'); @@ -488,6 +654,17 @@ export class AndroidSetup { packagesToInstall ); + // Download build-tools if using Appium + if (this.options.appium) { + const res = downloadSdkBuildTools( + getBinaryLocation(this.sdkRoot, this.platform, 'sdkmanager', true), + this.platform + ); + if (!res) { + result = false; + } + } + if (missingRequirements.includes('platforms')) { Logger.log('Creating platforms subdirectory...'); @@ -814,25 +991,49 @@ export class AndroidSetup { } } - sdkRootEnvSetInstructions() { + envSetInstructions(sdkRootEnv: string) { Logger.log(colors.red('IMPORTANT')); Logger.log(colors.red('---------')); - if (this.otherInfo.androidHomeInGlobalEnv && process.env.ANDROID_HOME === '') { - Logger.log(`${colors.cyan('ANDROID_HOME')} env is set to '' which is NOT a valid path!\n`); - Logger.log(`Please set ${colors.cyan('ANDROID_HOME')} to '${this.sdkRoot}' in your environment variables.`); - Logger.log('(As ANDROID_HOME env is already set, temporarily saving it to .env won\'t work.)\n'); - } else { + if (!sdkRootEnv) { + // ANDROID_HOME is either undefined or '' in system env and .env. + if (this.otherInfo.androidHomeInGlobalEnv) { + // ANDROID_HOME is set in system env, to ''. + Logger.log(`${colors.cyan('ANDROID_HOME')} env is set to '' which is NOT a valid path!\n`); + Logger.log(`Please set ${colors.cyan('ANDROID_HOME')} to '${this.sdkRoot}' in your system environment variables.`); + Logger.log('(As ANDROID_HOME is already set in system env, temporarily saving it to .env file won\'t work.)\n'); + } else { + Logger.log( + `${colors.cyan('ANDROID_HOME')} env was temporarily saved in ${colors.cyan( + '.env' + )} file (set to '${this.sdkRoot}').\n` + ); + Logger.log(`Please set ${colors.cyan( + 'ANDROID_HOME' + )} env to '${this.sdkRoot}' in your system environment variables and then delete it from ${colors.cyan('.env')} file.\n`); + } + } + + if (this.options.appium) { + // JAVA_HOME env was not found and we set it ourselves in .env (javaHomeFound=null). + // In case JAVA_HOME was found incorrect in system env (javaHomeFound=false), + // process should have exited with error. Logger.log( - `${colors.cyan('ANDROID_HOME')} env was temporarily saved in ${colors.cyan( + `${colors.cyan('JAVA_HOME')} env was temporarily saved in ${colors.cyan( '.env' - )} file (set to '${this.sdkRoot}').\n` + )} file (set to '${this.javaHome}').\n` ); Logger.log(`Please set ${colors.cyan( - 'ANDROID_HOME' - )} env to '${this.sdkRoot}' globally and then delete it from ${colors.cyan('.env')} file.`); + 'JAVA_HOME' + )} env to '${this.javaHome}' in your system environment variables and then delete it from ${colors.cyan('.env')} file.\n`); } - Logger.log('Doing this now might save you from future troubles.\n'); + Logger.log('Following the above instructions might save you from future troubles.\n'); + + this.envSetHelp(); + } + + envSetHelp() { + // Add platform-wise help or link a doc to help users set env variable. } } diff --git a/src/commands/android/interfaces.ts b/src/commands/android/interfaces.ts index eefe9c8..a64b66c 100644 --- a/src/commands/android/interfaces.ts +++ b/src/commands/android/interfaces.ts @@ -19,6 +19,7 @@ export type Platform = 'windows' | 'linux' | 'mac'; export interface OtherInfo { androidHomeInGlobalEnv: boolean; + javaHomeInGlobalEnv: boolean; } export interface SetupConfigs { diff --git a/src/commands/android/utils/common.ts b/src/commands/android/utils/common.ts index 92cf7fc..ff2281b 100644 --- a/src/commands/android/utils/common.ts +++ b/src/commands/android/utils/common.ts @@ -25,7 +25,7 @@ export const getBinaryNameForOS = (platform: Platform, binaryName: string) => { return binaryName; } - if (['sdkmanager', 'avdmanager'].includes(binaryName)) { + if (['sdkmanager', 'avdmanager', 'apksigner'].includes(binaryName)) { return `${binaryName}.bat`; } diff --git a/src/commands/android/utils/sdk.ts b/src/commands/android/utils/sdk.ts index 7da7182..88dbfcc 100644 --- a/src/commands/android/utils/sdk.ts +++ b/src/commands/android/utils/sdk.ts @@ -186,3 +186,50 @@ export const execBinarySync = ( return null; } }; + +export const getBuildToolsAvailableVersions = (buildToolsPath: string): string[] => { + if (!fs.existsSync(buildToolsPath)) { + return []; + } + + const buildToolsContent = fs.readdirSync(buildToolsPath); + const availableVersions = buildToolsContent.filter( + (name) => name.match(/^(\d+)(\.\d+){2}(-[a-z1-9]+)?/) !== null + ); + + return availableVersions; +}; + +export const downloadSdkBuildTools = ( + sdkManagerLocation: string, + platform: Platform +): boolean => { + console.log('Looking for the latest version of build-tools...'); + const versionStdout = execBinarySync( + sdkManagerLocation, + 'sdkmanager', + platform, + '--list' + ); + + if (versionStdout !== null) { + const versionMatch = versionStdout.match(/build-tools;(\d+)(\.\d+){2}(-[a-z1-9]+)?/g); + if (!versionMatch) { + console.log(` ${colors.red(symbols().fail)} Failed to get the latest version of build-tools.\n`); + + return false; + } + + const latestBuildTools = versionMatch.slice(-1); + console.log(); + + return installPackagesUsingSdkManager( + sdkManagerLocation, + platform, + latestBuildTools + ); + } + console.log(); + + return false; +}; diff --git a/src/index.ts b/src/index.ts index 0454c99..c15ec74 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,7 @@ export const run = () => { try { const argv = process.argv.slice(2); const {_: args, ...options} = minimist(argv, { - boolean: ['install', 'setup', 'help'], + boolean: ['install', 'setup', 'help', 'appium'], alias: { help: 'h', mode: 'm', diff --git a/tests/unit_tests/commands/android/testSdkRootFromEnv.js b/tests/unit_tests/commands/android/testGetXFromEnv.js similarity index 56% rename from tests/unit_tests/commands/android/testSdkRootFromEnv.js rename to tests/unit_tests/commands/android/testGetXFromEnv.js index 474c186..f36ea55 100644 --- a/tests/unit_tests/commands/android/testSdkRootFromEnv.js +++ b/tests/unit_tests/commands/android/testGetXFromEnv.js @@ -5,11 +5,346 @@ const mockery = require('mockery'); const path = require('path'); const os = require('os'); + +describe('test loadEnvFromDotEnv', function() { + beforeEach(() => { + mockery.enable({useCleanCache: true, warnOnReplace: false, warnOnUnregistered: false}); + + mockery.registerMock('./adb', {}); + + // Delete any set process.env + delete process.env.ANDROID_HOME; + delete process.env.JAVA_HOME; + }); + + afterEach(() => { + mockery.deregisterAll(); + mockery.resetCache(); + mockery.disable(); + + // Remove contents of .env file + const rootDir = path.join(__dirname, 'fixtures'); + fs.writeFileSync(path.join(rootDir, '.env'), ''); + }); + + test('ANDROID_HOME and JAVA_HOME set in env with ANDROID_HOME set in .env and no Appium', function() { + let dotenvRead = false; + mockery.registerMock('dotenv', { + config: (options) => { + if (fs.existsSync(options.path)) { + dotenvRead = true; + } + + dotenv_config(options); + } + }); + + process.env.ANDROID_HOME = 'path/to/android/sdk'; + process.env.JAVA_HOME = 'path/to/java'; + + const rootDir = path.join(__dirname, 'fixtures'); + fs.writeFileSync(path.join(rootDir, '.env'), '\nANDROID_HOME=path/to/random/sdk'); + + const {AndroidSetup} = require('../../../../src/commands/android/index'); + const androidSetup = new AndroidSetup({}, rootDir); + androidSetup.loadEnvFromDotEnv(); + + assert.strictEqual(androidSetup.otherInfo.androidHomeInGlobalEnv, true); + assert.strictEqual(androidSetup.otherInfo.javaHomeInGlobalEnv, false); + + assert.strictEqual(dotenvRead, true); + assert.strictEqual(process.env.ANDROID_HOME, 'path/to/android/sdk'); + assert.strictEqual(process.env.JAVA_HOME, 'path/to/java'); + }); + + test('JAVA_HOME set in env with Appium', function() { + process.env.JAVA_HOME = 'path/to/java'; + + const rootDir = path.join(__dirname, 'fixtures'); + + const {AndroidSetup} = require('../../../../src/commands/android/index'); + const androidSetup = new AndroidSetup({}, rootDir); + androidSetup.options.appium = true; + androidSetup.loadEnvFromDotEnv(); + + assert.strictEqual(androidSetup.otherInfo.androidHomeInGlobalEnv, false); + assert.strictEqual(androidSetup.otherInfo.javaHomeInGlobalEnv, true); + + assert.strictEqual(process.env.ANDROID_HOME, undefined); + assert.strictEqual(process.env.JAVA_HOME, 'path/to/java'); + }); + + test('ANDROID_HOME and JAVA_HOME not set in env but in .env', function() { + let dotenvRead = false; + mockery.registerMock('dotenv', { + config: (options) => { + if (fs.existsSync(options.path)) { + dotenvRead = true; + } + + dotenv_config(options); + } + }); + + const rootDir = path.join(__dirname, 'fixtures'); + + fs.writeFileSync(path.join(rootDir, '.env'), '\nANDROID_HOME=path/to/android/sdk\nJAVA_HOME=path/to/java'); + + const {AndroidSetup} = require('../../../../src/commands/android/index'); + const androidSetup = new AndroidSetup({}, rootDir); + androidSetup.loadEnvFromDotEnv(); + + assert.strictEqual(androidSetup.otherInfo.androidHomeInGlobalEnv, false); + assert.strictEqual(androidSetup.otherInfo.javaHomeInGlobalEnv, false); + + assert.strictEqual(dotenvRead, true); + assert.strictEqual(process.env.ANDROID_HOME, 'path/to/android/sdk'); + assert.strictEqual(process.env.JAVA_HOME, 'path/to/java'); + }); +}); + +describe('test isJavaHomeEnvSet', function() { + beforeEach(() => { + mockery.enable({useCleanCache: true, warnOnReplace: false, warnOnUnregistered: false}); + + mockery.registerMock('./adb', {}); + + // Delete any set process.env + delete process.env.ANDROID_HOME; + delete process.env.JAVA_HOME; + }); + + afterEach(() => { + mockery.deregisterAll(); + mockery.resetCache(); + mockery.disable(); + + // Remove contents of .env file + const rootDir = path.join(__dirname, 'fixtures'); + fs.writeFileSync(path.join(rootDir, '.env'), ''); + }); + + test('JAVA_HOME set correctly with bin inside (in env and .env)', () => { + const consoleOutput = []; + mockery.registerMock( + '../../logger', + class { + static log(...msgs) { + consoleOutput.push(...msgs); + } + } + ); + + const colorFn = (arg) => arg; + mockery.registerMock('ansi-colors', { + green: colorFn, + yellow: colorFn, + magenta: colorFn, + cyan: colorFn, + red: colorFn, + grey: colorFn + }); + + mockery.registerMock('fs', { + existsSync() { + return true; + } + }); + + const javaHomePath = 'path/to/jdk'; + process.env.JAVA_HOME = javaHomePath; + + const rootDir = path.join(__dirname, 'fixtures'); + + const {AndroidSetup} = require('../../../../src/commands/android/index'); + const androidSetup = new AndroidSetup({}, rootDir); + androidSetup.otherInfo.javaHomeInGlobalEnv = true; + const result = androidSetup.isJavaHomeEnvSet(); + + assert.strictEqual(result, true); + + const output = consoleOutput.toString(); + assert.strictEqual(output.includes(`JAVA_HOME is set to '${javaHomePath}'`), true); + assert.strictEqual(output.includes(`JAVA_HOME is set to '${javaHomePath}' (taken from .env)`), false); + assert.strictEqual(output.includes(`'bin' subfolder exists under '${javaHomePath}'\n`), true); + + // With JAVA_HOME in .env + consoleOutput.length = 0; + + androidSetup.otherInfo.javaHomeInGlobalEnv = false; + const result1 = androidSetup.isJavaHomeEnvSet(); + + assert.strictEqual(result1, true); + + const output1 = consoleOutput.toString(); + assert.strictEqual(output1.includes(`JAVA_HOME is set to '${javaHomePath}' (taken from .env)`), true); + assert.strictEqual(output1.includes(`'bin' subfolder exists under '${javaHomePath}'\n`), true); + }); + + test('JAVA_HOME set correctly with bin not found (in env and .env)', () => { + const consoleOutput = []; + mockery.registerMock( + '../../logger', + class { + static log(...msgs) { + consoleOutput.push(...msgs); + } + } + ); + + const colorFn = (arg) => arg; + mockery.registerMock('ansi-colors', { + green: colorFn, + yellow: colorFn, + magenta: colorFn, + cyan: colorFn, + red: colorFn, + grey: colorFn + }); + + mockery.registerMock('fs', { + existsSync(path) { + if (path.includes('bin')) { + return false; + } + + return true; + } + }); + + const javaHomePath = 'path/to/jdk'; + process.env.JAVA_HOME = javaHomePath; + + const rootDir = path.join(__dirname, 'fixtures'); + + const {AndroidSetup} = require('../../../../src/commands/android/index'); + const androidSetup = new AndroidSetup({}, rootDir); + androidSetup.otherInfo.javaHomeInGlobalEnv = true; + const result = androidSetup.isJavaHomeEnvSet(); + + assert.strictEqual(result, false); + + const output = consoleOutput.toString(); + assert.strictEqual(output.includes(`JAVA_HOME is set to '${javaHomePath}'`), true); + assert.strictEqual(output.includes(`JAVA_HOME is set to '${javaHomePath}' (taken from .env)`), false); + assert.strictEqual(output.includes(`'bin' subfolder does not exist under '${javaHomePath}'.`), true); + + // With JAVA_HOME in .env + consoleOutput.length = 0; + + androidSetup.otherInfo.javaHomeInGlobalEnv = false; + const result1 = androidSetup.isJavaHomeEnvSet(); + + assert.strictEqual(result1, null); + + const output1 = consoleOutput.toString(); + assert.strictEqual(output1.includes(`JAVA_HOME is set to '${javaHomePath}' (taken from .env)`), true); + assert.strictEqual(output1.includes(`'bin' subfolder does not exist under '${javaHomePath}'.`), true); + }); + + test('JAVA_HOME set but path does not exist (in env and .env)', () => { + const consoleOutput = []; + mockery.registerMock( + '../../logger', + class { + static log(...msgs) { + consoleOutput.push(...msgs); + } + } + ); + + const colorFn = (arg) => arg; + mockery.registerMock('ansi-colors', { + green: colorFn, + yellow: colorFn, + magenta: colorFn, + cyan: colorFn, + red: colorFn, + grey: colorFn + }); + + mockery.registerMock('fs', { + existsSync() { + return false; + } + }); + + const javaHomePath = 'path/to/jdk'; + process.env.JAVA_HOME = javaHomePath; + + const rootDir = path.join(__dirname, 'fixtures'); + + const {AndroidSetup} = require('../../../../src/commands/android/index'); + const androidSetup = new AndroidSetup({}, rootDir); + androidSetup.otherInfo.javaHomeInGlobalEnv = true; + const result = androidSetup.isJavaHomeEnvSet(); + + assert.strictEqual(result, false); + + const output = consoleOutput.toString(); + assert.strictEqual(output.includes(`JAVA_HOME is set to '${javaHomePath}' but this is NOT`), true); + + // With JAVA_HOME in .env + consoleOutput.length = 0; + + androidSetup.otherInfo.javaHomeInGlobalEnv = false; + const result1 = androidSetup.isJavaHomeEnvSet(); + + assert.strictEqual(result1, null); + + const output1 = consoleOutput.toString(); + assert.strictEqual(output1.includes(`JAVA_HOME is set to '${javaHomePath}' (taken from .env) but this is NOT`), true); + }); + + test('JAVA_HOME not set', () => { + const consoleOutput = []; + mockery.registerMock( + '../../logger', + class { + static log(...msgs) { + consoleOutput.push(...msgs); + } + } + ); + + const colorFn = (arg) => arg; + mockery.registerMock('ansi-colors', { + green: colorFn, + yellow: colorFn, + magenta: colorFn, + cyan: colorFn, + red: colorFn, + grey: colorFn + }); + + mockery.registerMock('fs', { + existsSync() { + return false; + } + }); + + const rootDir = path.join(__dirname, 'fixtures'); + + const {AndroidSetup} = require('../../../../src/commands/android/index'); + const androidSetup = new AndroidSetup({}, rootDir); + const result = androidSetup.isJavaHomeEnvSet(); + + assert.strictEqual(result, null); + + const output = consoleOutput.toString(); + assert.strictEqual(output.includes('JAVA_HOME env variable is NOT set!\n'), true); + }); +}); + describe('test getSdkRootFromEnv', function() { beforeEach(() => { mockery.enable({useCleanCache: true, warnOnReplace: false, warnOnUnregistered: false}); mockery.registerMock('./adb', {}); + + // Delete any set process.env + delete process.env.ANDROID_HOME; + delete process.env.JAVA_HOME; }); afterEach(() => { @@ -43,8 +378,6 @@ describe('test getSdkRootFromEnv', function() { grey: colorFn }); - const origAndroidHome = process.env.ANDROID_HOME; - const platformAndroidHome = (process.platform === 'win32') ? 'C:\\users\\nightwatch\\android_sdk' : '/Users/nightwatch/android_sdk'; @@ -54,6 +387,7 @@ describe('test getSdkRootFromEnv', function() { const {AndroidSetup} = require('../../../../src/commands/android/index'); const androidSetup = new AndroidSetup({}, rootDir); + androidSetup.loadEnvFromDotEnv(); const result = androidSetup.getSdkRootFromEnv(); assert.strictEqual(result, platformAndroidHome); @@ -61,8 +395,6 @@ describe('test getSdkRootFromEnv', function() { const output = consoleOutput.toString(); assert.strictEqual(output.includes(`ANDROID_HOME is set to '${platformAndroidHome}'\n`), true); - - process.env.ANDROID_HOME = origAndroidHome; }); test('when ANDROID_HOME defined in env with ~ path and in .env with absolute path', () => { @@ -105,12 +437,12 @@ describe('test getSdkRootFromEnv', function() { fs.writeFileSync(path.join(rootDir, '.env'), `ANDROID_HOME=${platformAbsoluteAndroidHome}`); // set ~ ANDROID_HOME to process.env - const origAndroidHome = process.env.ANDROID_HOME; const androidHome = path.join('~', 'android_sdk_tilde'); process.env.ANDROID_HOME = androidHome; const {AndroidSetup} = require('../../../../src/commands/android/index'); const androidSetup = new AndroidSetup({}, rootDir); + androidSetup.loadEnvFromDotEnv(); const result = androidSetup.getSdkRootFromEnv(); const absoluteAndroidHome = path.join(os.homedir(), 'android_sdk_tilde'); @@ -120,8 +452,6 @@ describe('test getSdkRootFromEnv', function() { const output = consoleOutput.toString(); assert.strictEqual(output.includes(`ANDROID_HOME is set to '${absoluteAndroidHome}'\n`), true); - - process.env.ANDROID_HOME = origAndroidHome; }); test('when ANDROID_HOME defined in env with relative path and in .env with absolute path', () => { @@ -164,12 +494,12 @@ describe('test getSdkRootFromEnv', function() { fs.writeFileSync(path.join(rootDir, '.env'), `ANDROID_HOME=${platformAbsoluteAndroidHome}`); // set relative ANDROID_HOME to process.env - const origAndroidHome = process.env.ANDROID_HOME; const androidHome = 'android_sdk_relative'; process.env.ANDROID_HOME = androidHome; const {AndroidSetup} = require('../../../../src/commands/android/index'); const androidSetup = new AndroidSetup({}, rootDir); + androidSetup.loadEnvFromDotEnv(); const result = androidSetup.getSdkRootFromEnv(); const absoluteAndroidHome = path.join(rootDir, androidHome); @@ -181,8 +511,6 @@ describe('test getSdkRootFromEnv', function() { const output = consoleOutput.toString(); assert.strictEqual(output.includes(`ANDROID_HOME is set to '${androidHome}' which is NOT`), true); assert.strictEqual(output.includes(`Considering ANDROID_HOME to be '${absoluteAndroidHome}'\n`), true); - - process.env.ANDROID_HOME = origAndroidHome; }); test('when ANDROID_HOME defined in env with empty path and in .env with absolute path', () => { @@ -225,12 +553,12 @@ describe('test getSdkRootFromEnv', function() { fs.writeFileSync(path.join(rootDir, '.env'), `ANDROID_HOME=${platformAbsoluteAndroidHome}`); // set ANDROID_HOME='' to process.env - const origAndroidHome = process.env.ANDROID_HOME; const androidHome = ''; process.env.ANDROID_HOME = androidHome; const {AndroidSetup} = require('../../../../src/commands/android/index'); const androidSetup = new AndroidSetup({}, rootDir); + androidSetup.loadEnvFromDotEnv(); const result = androidSetup.getSdkRootFromEnv(); assert.strictEqual(result, ''); @@ -239,8 +567,6 @@ describe('test getSdkRootFromEnv', function() { const output = consoleOutput.toString(); assert.strictEqual(output.includes('ANDROID_HOME is set to \'\' which is NOT a valid path!'), true); - - process.env.ANDROID_HOME = origAndroidHome; }); test('when ANDROID_HOME not defined in env and not defined in .env as well', () => { @@ -265,13 +591,13 @@ describe('test getSdkRootFromEnv', function() { }); // set ANDROID_HOME to undefined in process.env - const origAndroidHome = process.env.ANDROID_HOME; delete process.env.ANDROID_HOME; const rootDir = path.join(__dirname, 'fixtures'); const {AndroidSetup} = require('../../../../src/commands/android/index'); const androidSetup = new AndroidSetup({}, rootDir); + androidSetup.loadEnvFromDotEnv(); const result = androidSetup.getSdkRootFromEnv(); assert.strictEqual(result, ''); @@ -279,8 +605,6 @@ describe('test getSdkRootFromEnv', function() { const output = consoleOutput.toString(); assert.strictEqual(output.includes('ANDROID_HOME environment variable is NOT set!'), true); - - process.env.ANDROID_HOME = origAndroidHome; }); test('when ANDROID_HOME not defined in env but defined in .env with absolute path', () => { @@ -323,11 +647,11 @@ describe('test getSdkRootFromEnv', function() { fs.writeFileSync(path.join(rootDir, '.env'), `ANDROID_HOME=${platformAbsoluteAndroidHome}`); // set ANDROID_HOME to undefined in process.env - const origAndroidHome = process.env.ANDROID_HOME; delete process.env.ANDROID_HOME; const {AndroidSetup} = require('../../../../src/commands/android/index'); const androidSetup = new AndroidSetup({}, rootDir); + androidSetup.loadEnvFromDotEnv(); const result = androidSetup.getSdkRootFromEnv(); assert.strictEqual(result, platformAbsoluteAndroidHome); @@ -336,8 +660,6 @@ describe('test getSdkRootFromEnv', function() { const output = consoleOutput.toString(); assert.strictEqual(output.includes(`ANDROID_HOME is set to '${platformAbsoluteAndroidHome}' (taken from .env)`), true); - - process.env.ANDROID_HOME = origAndroidHome; }); test('when ANDROID_HOME not defined in env but defined in .env with relative path', () => { @@ -379,11 +701,11 @@ describe('test getSdkRootFromEnv', function() { fs.writeFileSync(path.join(rootDir, '.env'), `ANDROID_HOME=${androidHome}`); // set ANDROID_HOME to undefined in process.env - const origAndroidHome = process.env.ANDROID_HOME; delete process.env.ANDROID_HOME; const {AndroidSetup} = require('../../../../src/commands/android/index'); const androidSetup = new AndroidSetup({}, rootDir); + androidSetup.loadEnvFromDotEnv(); const result = androidSetup.getSdkRootFromEnv(); const absoluteAndroidHome = path.join(rootDir, androidHome); @@ -394,8 +716,6 @@ describe('test getSdkRootFromEnv', function() { const output = consoleOutput.toString(); assert.strictEqual(output.includes(`ANDROID_HOME is set to '${androidHome}' (taken from .env) which is NOT`), true); assert.strictEqual(output.includes(`Considering ANDROID_HOME to be '${absoluteAndroidHome}'\n`), true); - - process.env.ANDROID_HOME = origAndroidHome; }); test('when ANDROID_HOME not defined in env but defined in .env with empty path', () => { @@ -437,11 +757,11 @@ describe('test getSdkRootFromEnv', function() { fs.writeFileSync(path.join(rootDir, '.env'), `ANDROID_HOME=${androidHome}`); // set ANDROID_HOME to undefined in process.env - const origAndroidHome = process.env.ANDROID_HOME; delete process.env.ANDROID_HOME; const {AndroidSetup} = require('../../../../src/commands/android/index'); const androidSetup = new AndroidSetup({}, rootDir); + androidSetup.loadEnvFromDotEnv(); const result = androidSetup.getSdkRootFromEnv(); assert.strictEqual(result, ''); @@ -450,7 +770,5 @@ describe('test getSdkRootFromEnv', function() { const output = consoleOutput.toString(); assert.strictEqual(output.includes('ANDROID_HOME is set to \'\' (taken from .env) which is NOT a valid path!'), true); - - process.env.ANDROID_HOME = origAndroidHome; }); }); diff --git a/tests/unit_tests/commands/android/testIndex.js b/tests/unit_tests/commands/android/testIndex.js index 7fe26cf..0ae7c70 100644 --- a/tests/unit_tests/commands/android/testIndex.js +++ b/tests/unit_tests/commands/android/testIndex.js @@ -165,11 +165,125 @@ describe('test checkJavaInstallation', function() { assert.strictEqual(commandsExecuted[0], 'java -version'); const output = consoleOutput.toString(); - assert.strictEqual(output.includes('Java Development Kit is required'), true); + assert.strictEqual(output.includes('Java Development Kit v9 or above is required'), true); assert.strictEqual(output.includes('Make sure Java is installed by running java -version'), true); }); }); +describe('test getJavaHomeFromUser', function() { + beforeEach(() => { + mockery.enable({useCleanCache: true, warnOnReplace: false, warnOnUnregistered: false}); + + mockery.registerMock('./adb', {}); + }); + + afterEach(() => { + mockery.deregisterAll(); + mockery.resetCache(); + mockery.disable(); + }); + + test('JAVA_HOME auto-detected on mac and normally asked on others', async () => { + const commandsExecuted = []; + mockery.registerMock('child_process', { + execSync(command) { + commandsExecuted.push(command); + + return 'path/to/auto/detected/jdk'; + } + }); + + let javaHomePassedToPrompt = false; + mockery.registerMock('inquirer', { + prompt: async (questions, answers) => { + if (!!answers.javaHome) { + javaHomePassedToPrompt = true; + + return answers; + } + + return {javaHome: 'path/to/prompt/jdk'}; + } + }); + + const rootDir = path.join(__dirname, 'fixtures'); + + let appendFileSyncCalledWith;; + mockery.registerMock('fs', { + appendFileSync(path, content) { + appendFileSyncCalledWith = content; + } + }); + + mockery.registerMock('./adb', {}); + + const {AndroidSetup} = require('../../../../src/commands/android/index'); + const androidSetup = new AndroidSetup({}, rootDir); + const javaHome = await androidSetup.getJavaHomeFromUser(); + + if (androidSetup.platform === 'mac') { + assert.strictEqual(javaHomePassedToPrompt, true); + assert.strictEqual(javaHome, 'path/to/auto/detected/jdk'); + assert.deepStrictEqual(commandsExecuted, ['/usr/libexec/java_home']); + assert.strictEqual(appendFileSyncCalledWith, '\nJAVA_HOME=path/to/auto/detected/jdk'); + } else { + assert.strictEqual(javaHomePassedToPrompt, false); + assert.strictEqual(javaHome, 'path/to/prompt/jdk'); + assert.deepStrictEqual(commandsExecuted, []); + assert.strictEqual(appendFileSyncCalledWith, '\nJAVA_HOME=path/to/prompt/jdk'); + } + }); + + test('JAVA_HOME normally asked on all platforms if auto-detection fail on mac', async () => { + const commandsExecuted = []; + mockery.registerMock('child_process', { + execSync(command) { + commandsExecuted.push(command); + + throw new Error(); + } + }); + + let javaHomePassedToPrompt = false; + mockery.registerMock('inquirer', { + prompt: async (questions, answers) => { + if (!!answers.javaHome) { + javaHomePassedToPrompt = true; + + return answers; + } + + return {javaHome: 'path/to/prompt/jdk'}; + } + }); + + const rootDir = path.join(__dirname, 'fixtures'); + + let appendFileSyncCalledWith;; + mockery.registerMock('fs', { + appendFileSync(path, content) { + appendFileSyncCalledWith = content; + } + }); + + mockery.registerMock('./adb', {}); + + const {AndroidSetup} = require('../../../../src/commands/android/index'); + const androidSetup = new AndroidSetup({}, rootDir); + const javaHome = await androidSetup.getJavaHomeFromUser(); + + if (androidSetup.platform === 'mac') { + assert.deepStrictEqual(commandsExecuted, ['/usr/libexec/java_home']); + } else { + assert.deepStrictEqual(commandsExecuted, []); + } + + assert.strictEqual(javaHomePassedToPrompt, false); + assert.strictEqual(javaHome, 'path/to/prompt/jdk'); + assert.strictEqual(appendFileSyncCalledWith, '\nJAVA_HOME=path/to/prompt/jdk'); + }); +}); + describe('test getSdkRootFromUser', function() { beforeEach(() => { mockery.enable({useCleanCache: true, warnOnReplace: false, warnOnUnregistered: false}); @@ -296,7 +410,7 @@ describe('test getSdkRootFromUser', function() { assert.strictEqual(appendFileSyncCalled, true); assert.strictEqual(envPath, path.join(rootDir, '.env')); - assert.strictEqual(envContent.includes(`ANDROID_HOME=${androidHomeAbsolute}\n`), true); + assert.strictEqual(envContent.includes(`ANDROID_HOME=${androidHomeAbsolute}`), true); }); }); @@ -752,6 +866,13 @@ describe('test verifySetup', function() { } }); + let buildToolsChecked = false; + mockery.registerMock('./utils/sdk', { + getBuildToolsAvailableVersions() { + buildToolsChecked = true; + } + }); + const {AndroidSetup} = require('../../../../src/commands/android/index'); const androidSetup = new AndroidSetup(); @@ -789,6 +910,7 @@ describe('test verifySetup', function() { assert.deepStrictEqual(binariesCheckedForPresent, ['adb']); assert.strictEqual(platformFolderChecked, false); + assert.strictEqual(buildToolsChecked, false); assert.strictEqual(avdChecked, false); // adb binary not present assert.strictEqual(checkBinariesWorkingCalled, false); @@ -833,6 +955,13 @@ describe('test verifySetup', function() { } }); + let buildToolsChecked = false; + mockery.registerMock('./utils/sdk', { + getBuildToolsAvailableVersions() { + buildToolsChecked = true; + } + }); + const {AndroidSetup} = require('../../../../src/commands/android/index'); const androidSetup = new AndroidSetup(); @@ -870,6 +999,7 @@ describe('test verifySetup', function() { assert.deepStrictEqual(binariesCheckedForPresent, ['adb']); assert.strictEqual(platformFolderChecked, false); + assert.strictEqual(buildToolsChecked, false); assert.strictEqual(avdChecked, false); // adb binary present and working assert.strictEqual(checkBinariesWorkingCalled, true); @@ -881,6 +1011,99 @@ describe('test verifySetup', function() { assert.strictEqual(output.includes('Verifying the setup requirements for real devices...'), true); }); + test('for real mode and Appium with adb binary present not working and build-tools not present', () => { + const consoleOutput = []; + mockery.registerMock( + '../../logger', + class { + static log(...msgs) { + consoleOutput.push(...msgs); + } + } + ); + + const colorFn = (arg) => arg; + mockery.registerMock('ansi-colors', { + green: colorFn, + yellow: colorFn, + magenta: colorFn, + cyan: colorFn, + red: colorFn, + grey: colorFn + }); + + let platformFolderChecked = false; + mockery.registerMock('fs', { + existsSync(path, ...args) { + if (path.endsWith('platforms')) { + platformFolderChecked = true; + + return false; + } + + return fs.existsSync(path, ...args); + } + }); + + let buildToolsChecked = false; + mockery.registerMock('./utils/sdk', { + getBuildToolsAvailableVersions() { + buildToolsChecked = true; + + return []; + } + }) + + const {AndroidSetup} = require('../../../../src/commands/android/index'); + const androidSetup = new AndroidSetup({appium: true}); + + const binariesCheckedForPresent = []; + androidSetup.checkBinariesPresent = (binaries) => { + binariesCheckedForPresent.push(...binaries); + + // all listed binaries present + return []; + }; + + let checkBinariesWorkingCalled = false; + const binariesCheckedForWorking = []; + androidSetup.checkBinariesWorking = (binaries) => { + checkBinariesWorkingCalled = true; + binariesCheckedForWorking.push(...binaries); + + // adb not working + return ['adb']; + }; + + let avdChecked = false; + androidSetup.verifyAvdPresent = () => { + avdChecked = true; + + return false; + }; + + let adbRunningChecked = false; + androidSetup.verifyAdbRunning = () => { + adbRunningChecked = true; + }; + + const missingRequirements = androidSetup.verifySetup({mode: 'real'}); + + assert.deepStrictEqual(binariesCheckedForPresent, ['adb']); + assert.strictEqual(platformFolderChecked, false); + assert.strictEqual(buildToolsChecked, true); + assert.strictEqual(avdChecked, false); + // adb binary present not working + assert.strictEqual(checkBinariesWorkingCalled, true); + assert.deepStrictEqual(binariesCheckedForWorking, ['adb']); + assert.strictEqual(adbRunningChecked, false); + assert.deepStrictEqual(missingRequirements, ['build-tools', 'adb']); + + const output = consoleOutput.toString(); + assert.strictEqual(output.includes('Verifying the setup requirements for real devices...'), true); + assert.strictEqual(output.includes('Android Build Tools not present at'), true); + }); + test('for emulator mode and emulator, platforms not present and adb not working', () => { const consoleOutput = []; mockery.registerMock( @@ -915,6 +1138,13 @@ describe('test verifySetup', function() { } }); + let buildToolsChecked = false; + mockery.registerMock('./utils/sdk', { + getBuildToolsAvailableVersions() { + buildToolsChecked = true; + } + }); + const {AndroidSetup} = require('../../../../src/commands/android/index'); const androidSetup = new AndroidSetup(); @@ -952,6 +1182,7 @@ describe('test verifySetup', function() { assert.deepStrictEqual(binariesCheckedForPresent, ['adb', 'avdmanager', 'emulator']); assert.strictEqual(platformFolderChecked, true); + assert.strictEqual(buildToolsChecked, false); assert.strictEqual(avdChecked, true); assert.strictEqual(checkBinariesWorkingCalled, true); @@ -965,7 +1196,7 @@ describe('test verifySetup', function() { assert.strictEqual(output.includes('AVD is present and ready to be used.'), true); }); - test('for both modes and AVD not present and everything working', () => { + test('for both modes with Appium and AVD not present and everything working', () => { const consoleOutput = []; mockery.registerMock( '../../logger', @@ -999,8 +1230,17 @@ describe('test verifySetup', function() { } }); + let buildToolsChecked = false; + mockery.registerMock('./utils/sdk', { + getBuildToolsAvailableVersions() { + buildToolsChecked = true; + + return ['31.0.1']; + } + }) + const {AndroidSetup} = require('../../../../src/commands/android/index'); - const androidSetup = new AndroidSetup(); + const androidSetup = new AndroidSetup({appium: true}); const binariesCheckedForPresent = []; androidSetup.checkBinariesPresent = (binaries) => { @@ -1036,6 +1276,7 @@ describe('test verifySetup', function() { assert.deepStrictEqual(binariesCheckedForPresent, ['adb', 'avdmanager', 'emulator']); assert.strictEqual(platformFolderChecked, true); + assert.strictEqual(buildToolsChecked, true); assert.strictEqual(avdChecked, true); assert.strictEqual(checkBinariesWorkingCalled, true); @@ -1045,6 +1286,8 @@ describe('test verifySetup', function() { const output = consoleOutput.toString(); assert.strictEqual(output.includes('Verifying the setup requirements for real devices/emulator...'), true); + assert.strictEqual(output.includes('Android Build Tools present at'), true); + assert.strictEqual(output.includes('Available versions: 31.0.1'), true); assert.strictEqual(output.includes('platforms subdirectory is present at'), true); assert.strictEqual(output.includes('AVD not found.'), true); }); @@ -1083,6 +1326,13 @@ describe('test verifySetup', function() { } }); + let buildToolsChecked = false; + mockery.registerMock('./utils/sdk', { + getBuildToolsAvailableVersions() { + buildToolsChecked = true; + } + }); + const {AndroidSetup} = require('../../../../src/commands/android/index'); const androidSetup = new AndroidSetup(); @@ -1120,6 +1370,7 @@ describe('test verifySetup', function() { assert.deepStrictEqual(binariesCheckedForPresent, ['adb', 'avdmanager', 'emulator']); assert.strictEqual(platformFolderChecked, true); + assert.strictEqual(buildToolsChecked, false); assert.strictEqual(avdChecked, true); assert.strictEqual(checkBinariesWorkingCalled, true); @@ -1147,7 +1398,7 @@ describe('test setupAndroid', function() { mockery.disable(); }); - test('for real mode with adb, sdkmanager not present', async () => { + test('for real mode and Appium with adb, sdkmanager not present and build-tools present', async () => { const consoleOutput = []; mockery.registerMock( '../../logger', @@ -1177,6 +1428,7 @@ describe('test setupAndroid', function() { let cmdlineToolsDownloaded = false; const packagesInstalled = []; let avdCreationInitiated = false; + let buildToolsDownloaded = false; mockery.registerMock('./utils/sdk', { downloadAndSetupAndroidSdk: () => { cmdlineToolsDownloaded = true; @@ -1190,6 +1442,11 @@ describe('test setupAndroid', function() { avdCreationInitiated = true; return ''; + }, + downloadSdkBuildTools() { + buildToolsDownloaded = true; + + return true; } }); @@ -1201,7 +1458,7 @@ describe('test setupAndroid', function() { }); const {AndroidSetup} = require('../../../../src/commands/android/index'); - const androidSetup = new AndroidSetup(); + const androidSetup = new AndroidSetup({appium: true}); const binariesCheckedForWorking = []; androidSetup.checkBinariesWorking = (binaries) => { @@ -1229,6 +1486,7 @@ describe('test setupAndroid', function() { assert.strictEqual(cmdlineToolsDownloaded, true); assert.deepStrictEqual(packagesInstalled, ['platform-tools']); assert.strictEqual(platformFolderCreated, false); + assert.strictEqual(buildToolsDownloaded, true); assert.strictEqual(avdChecked, false); assert.strictEqual(avdCreationInitiated, false); assert.strictEqual(adbRunningChecked, true); @@ -1241,7 +1499,7 @@ describe('test setupAndroid', function() { assert.strictEqual(output.includes('Downloading cmdline-tools...'), true); }); - test('for emulator mode with avdmanager, platforms, AVD not present', async () => { + test('for emulator mode and Appium with avdmanager, platforms, AVD not present', async () => { const consoleOutput = []; mockery.registerMock( '../../logger', @@ -1271,6 +1529,7 @@ describe('test setupAndroid', function() { let cmdlineToolsDownloaded = false; const packagesInstalled = []; let avdCreationInitiated = false; + let buildToolsDownloaded = false; mockery.registerMock('./utils/sdk', { downloadAndSetupAndroidSdk: () => { cmdlineToolsDownloaded = true; @@ -1284,6 +1543,11 @@ describe('test setupAndroid', function() { avdCreationInitiated = true; return ''; + }, + downloadSdkBuildTools() { + buildToolsDownloaded = true; + + return true; } }); @@ -1295,7 +1559,7 @@ describe('test setupAndroid', function() { }); const {AndroidSetup} = require('../../../../src/commands/android/index'); - const androidSetup = new AndroidSetup(); + const androidSetup = new AndroidSetup({appium: true}); const binariesCheckedForWorking = []; androidSetup.checkBinariesWorking = (binaries) => { @@ -1323,6 +1587,7 @@ describe('test setupAndroid', function() { assert.strictEqual(cmdlineToolsDownloaded, true); assert.deepStrictEqual(packagesInstalled, ['system-images;android-30;google_apis;x86_64', 'emulator']); // emulator updated assert.strictEqual(platformFolderCreated, true); + assert.strictEqual(buildToolsDownloaded, true); assert.strictEqual(avdChecked, true); assert.strictEqual(avdCreationInitiated, true); assert.strictEqual(adbRunningChecked, true); @@ -1369,6 +1634,7 @@ describe('test setupAndroid', function() { let cmdlineToolsDownloaded = false; const packagesToInstall = []; let avdCreationInitiated = false; + let buildToolsDownloaded = false; mockery.registerMock('./utils/sdk', { downloadAndSetupAndroidSdk: () => { cmdlineToolsDownloaded = true; @@ -1382,6 +1648,11 @@ describe('test setupAndroid', function() { avdCreationInitiated = true; return ''; + }, + downloadSdkBuildTools() { + buildToolsDownloaded = true; + + return true; } }); @@ -1421,6 +1692,7 @@ describe('test setupAndroid', function() { assert.strictEqual(cmdlineToolsDownloaded, true); assert.deepStrictEqual(packagesToInstall, ['emulator']); assert.strictEqual(platformFolderCreated, false); + assert.strictEqual(buildToolsDownloaded, false); assert.strictEqual(avdChecked, false); assert.strictEqual(avdCreationInitiated, false); assert.strictEqual(adbRunningChecked, true); @@ -1465,6 +1737,7 @@ describe('test setupAndroid', function() { let cmdlineToolsDownloaded = false; const packagesToInstall = []; let avdCreationInitiated = false; + let buildToolsDownloaded = false; mockery.registerMock('./utils/sdk', { downloadAndSetupAndroidSdk: () => { cmdlineToolsDownloaded = true; @@ -1478,6 +1751,11 @@ describe('test setupAndroid', function() { avdCreationInitiated = true; return null; + }, + downloadSdkBuildTools() { + buildToolsDownloaded = true; + + return true; } }); @@ -1517,6 +1795,7 @@ describe('test setupAndroid', function() { assert.strictEqual(cmdlineToolsDownloaded, false); assert.deepStrictEqual(packagesToInstall, ['system-images;android-30;google_apis;x86_64', 'emulator']); // emulator updated assert.strictEqual(platformFolderCreated, false); + assert.strictEqual(buildToolsDownloaded, false); assert.strictEqual(avdChecked, true); assert.strictEqual(avdCreationInitiated, true); assert.strictEqual(adbRunningChecked, true); @@ -1532,7 +1811,7 @@ describe('test setupAndroid', function() { assert.strictEqual(output.includes('Success! AVD "nightwatch-android-11" created successfully!'), false); }); - test('for both modes with system-image not installed but avd already present', async () => { + test('for both modes and Appium with system-image not installed but avd already present and build-tools not present and failed', async () => { const consoleOutput = []; mockery.registerMock( '../../logger', @@ -1562,6 +1841,7 @@ describe('test setupAndroid', function() { let cmdlineToolsDownloaded = false; const packagesToInstall = []; let avdCreationInitiated = false; + let buildToolsDownloaded = false; mockery.registerMock('./utils/sdk', { downloadAndSetupAndroidSdk: () => { cmdlineToolsDownloaded = true; @@ -1575,6 +1855,11 @@ describe('test setupAndroid', function() { avdCreationInitiated = true; return 'something (stdout)'; + }, + downloadSdkBuildTools() { + buildToolsDownloaded = true; + + return false; } }); @@ -1586,7 +1871,7 @@ describe('test setupAndroid', function() { }); const {AndroidSetup} = require('../../../../src/commands/android/index'); - const androidSetup = new AndroidSetup(); + const androidSetup = new AndroidSetup({appium: true}); const binariesCheckedForWorking = []; androidSetup.checkBinariesWorking = (binaries) => { @@ -1608,17 +1893,18 @@ describe('test setupAndroid', function() { adbRunningChecked = true; }; - const result = await androidSetup.setupAndroid({mode: 'both'}, ['nightwatch-android-11']); + const result = await androidSetup.setupAndroid({mode: 'both'}, ['nightwatch-android-11', 'build-tools']); assert.deepStrictEqual(binariesCheckedForWorking, ['sdkmanager']); assert.strictEqual(cmdlineToolsDownloaded, false); assert.deepStrictEqual(packagesToInstall, ['system-images;android-30;google_apis;x86_64', 'emulator']); // emulator updated assert.strictEqual(platformFolderCreated, false); + assert.strictEqual(buildToolsDownloaded, true); assert.strictEqual(avdChecked, true); assert.strictEqual(avdCreationInitiated, false); assert.strictEqual(adbRunningChecked, true); - // avd creation failed - assert.strictEqual(result, true); + // build-tools failed + assert.strictEqual(result, false); const output = consoleOutput.toString(); assert.strictEqual(output.includes('Setting up missing requirements for real devices/emulator...'), true);