diff --git a/config.json b/config.json index 0b175b1b..5d140093 100644 --- a/config.json +++ b/config.json @@ -3,7 +3,8 @@ "selenium": "2.53.0", "chromedriver": "2.21", "iedriver": "2.53.1", - "androidsdk": "24.4.1" + "androidsdk": "24.4.1", + "appium": "1.5.3" }, "cdnUrls": { "selenium": "https://selenium-release.storage.googleapis.com/", diff --git a/lib/binaries/appium.ts b/lib/binaries/appium.ts new file mode 100644 index 00000000..810a3d38 --- /dev/null +++ b/lib/binaries/appium.ts @@ -0,0 +1,36 @@ +import * as child_process from 'child_process'; +import * as rimraf from 'rimraf'; +import * as path from 'path'; +import {arch, type} from 'os'; + +import {Binary, OS} from './binary'; +import {Config} from '../config'; + +/** + * The appium binary. + */ +export class Appium extends Binary { + static os = [OS.Windows_NT, OS.Linux, OS.Darwin]; + static id = 'appium'; + static versionDefault = Config.binaryVersions().appium; + static isDefault = false; + static shortName = ['appium']; + + constructor() { + super(); + this.name = 'appium'; + this.versionCustom = Appium.versionDefault; + this.prefixDefault = 'appium-'; + this.suffixDefault = ''; + } + + id(): string { return Appium.id; } + + versionDefault(): string { return Appium.versionDefault; } + + executableSuffix(): string { return ''; } + + remove(sdkPath: string): void { + rimraf.sync(sdkPath); + } +} diff --git a/lib/binaries/index.ts b/lib/binaries/index.ts index 2632bda3..3dbfb4a6 100644 --- a/lib/binaries/index.ts +++ b/lib/binaries/index.ts @@ -2,4 +2,5 @@ export * from './binary'; export * from './chrome_driver'; export * from './ie_driver'; export * from './android_sdk'; +export * from './appium'; export * from './stand_alone'; diff --git a/lib/cmds/initialize.ts b/lib/cmds/initialize.ts index 9bafa1a8..5c38e22f 100644 --- a/lib/cmds/initialize.ts +++ b/lib/cmds/initialize.ts @@ -230,3 +230,14 @@ export function android(sdkPath: string, apiLevels: string[], abis: string[], logger.info('android-sdk: Initialization complete'); }).done(); }; + +export function iOS(logger: Logger) { + if (os.type() != 'Darwin') { + throw new Error('Must be on a Mac to simulate iOS devices.'); + } + try { + fs.statSync('/Applications/Xcode.app'); + } catch(e) { + logger.warn('You must install the xcode commandline tools!'); + } +} diff --git a/lib/cmds/opts.ts b/lib/cmds/opts.ts index 64668e00..f7c1855d 100644 --- a/lib/cmds/opts.ts +++ b/lib/cmds/opts.ts @@ -1,6 +1,6 @@ import {Config} from '../config'; import {Cli, Option, Options} from '../cli'; -import {ChromeDriver, IEDriver, AndroidSDK, StandAlone} from '../binaries'; +import {ChromeDriver, IEDriver, AndroidSDK, Appium, StandAlone} from '../binaries'; export const OUT_DIR = 'out_dir'; export const SELENIUM_PORT = 'seleniumPort'; @@ -15,10 +15,12 @@ export const IE = 'ie'; export const IE32 = 'ie32'; export const EDGE = 'edge'; export const ANDROID = 'android'; +export const IOS = 'ios'; export const VERSIONS_CHROME = 'versions.chrome'; export const VERSIONS_STANDALONE = 'versions.standalone'; export const VERSIONS_IE = 'versions.ie'; export const VERSIONS_ANDROID = 'versions.android'; +export const VERSIONS_APPIUM = 'versions.appium'; export const CHROME_LOGS = 'chrome_logs'; export const ANDROID_API_LEVELS = 'android-api-levels'; export const ANDROID_ABIS = 'android-abis'; @@ -43,9 +45,11 @@ opts[IE] = new Option(IE, 'Install or update ie driver', 'boolean', IEDriver.isD opts[IE32] = new Option(IE32, 'Install or update 32-bit ie driver', 'boolean', IEDriver.isDefault); opts[EDGE] = new Option(EDGE, 'Use installed Microsoft Edge driver', 'string', 'C:\\Program Files (x86)\\Microsoft Web Driver\\MicrosoftWebDriver.exe'); opts[ANDROID] = new Option(ANDROID, 'Update/use the android sdk', 'boolean', AndroidSDK.isDefault); +opts[IOS] = new Option(IOS, 'Update the iOS sdk', 'boolean', false); opts[VERSIONS_CHROME] = new Option(VERSIONS_CHROME, 'Optional chrome driver version', 'string', ChromeDriver.versionDefault); opts[VERSIONS_ANDROID] = new Option(VERSIONS_ANDROID, 'Optional android sdk version', 'string', AndroidSDK.versionDefault); opts[VERSIONS_STANDALONE] = new Option(VERSIONS_STANDALONE, 'Optional seleniuim standalone server version', 'string', StandAlone.versionDefault); +opts[VERSIONS_APPIUM] = new Option(VERSIONS_APPIUM, 'Optional appium version', 'string', Appium.versionDefault); opts[VERSIONS_IE] = new Option(VERSIONS_IE, 'Optional internet explorer driver version', 'string', IEDriver.versionDefault); opts[CHROME_LOGS] = new Option(CHROME_LOGS, 'File path to chrome logs', 'string', undefined); opts[ANDROID_API_LEVELS] = new Option(ANDROID_API_LEVELS, 'Which versions of the android API you want to emulate', 'string', AndroidSDK.DEFAULT_API_LEVELS); diff --git a/lib/cmds/start.ts b/lib/cmds/start.ts index c7ebe455..518f0983 100644 --- a/lib/cmds/start.ts +++ b/lib/cmds/start.ts @@ -10,7 +10,8 @@ import * as Opt from './'; import {Config} from '../config'; import {FileManager} from '../files'; import {Logger, Options, Program} from '../cli'; -import {BinaryMap, Binary, ChromeDriver, IEDriver, AndroidSDK, StandAlone} from '../binaries'; +import {BinaryMap, Binary, ChromeDriver, IEDriver, AndroidSDK, Appium, StandAlone} from + '../binaries'; let logger = new Logger('start'); let prog = new Program() @@ -23,11 +24,16 @@ let prog = new Program() .addOption(Opts[Opt.VERSIONS_STANDALONE]) .addOption(Opts[Opt.VERSIONS_CHROME]) .addOption(Opts[Opt.VERSIONS_ANDROID]) + .addOption(Opts[Opt.VERSIONS_APPIUM]) .addOption(Opts[Opt.CHROME_LOGS]) .addOption(Opts[Opt.ANDROID]) .addOption(Opts[Opt.AVDS]) .addOption(Opts[Opt.AVD_USE_SNAPSHOTS]); +if (os.type() === 'Darwin') { + prog.addOption(Opts[Opt.IOS]); +} + if (os.type() === 'Windows_NT') { prog.addOption(Opts[Opt.VERSIONS_IE]); prog.addOption(Opts[Opt.EDGE]); @@ -83,6 +89,7 @@ function start(options: Options) { binaries[IEDriver.id].versionCustom = options[Opt.VERSIONS_IE].getString(); } binaries[AndroidSDK.id].versionCustom = options[Opt.VERSIONS_ANDROID].getString(); + binaries[Appium.id].versionCustom = options[Opt.VERSIONS_APPIUM].getString(); let downloadedBinaries = FileManager.downloadedBinaries(outputDir); if (downloadedBinaries[StandAlone.id] == null) { @@ -118,11 +125,14 @@ function start(options: Options) { let avds = options[Opt.AVDS].getString(); startAndroid(outputDir, binaries[AndroidSDK.id], avds.split(','), options[Opt.AVD_USE_SNAPSHOTS].getBoolean(), - options[Opt.APPIUM_PORT].getString()); - startAppium(outputDir, options[Opt.APPIUM_PORT].getString()); + options[Opt.AVD_PORT].getString()); } else { + logger.warn('Not starting android because it is not installed'); } } + if (downloadedBinaries[Appium.id] != null) { + startAppium(outputDir, binaries[Appium.id], options[Opt.APPIUM_PORT].getString()); + } // log the command to launch selenium server let argsToString = ''; @@ -204,9 +214,9 @@ function killAndroid() { // Manage appium process let appiumProcess: childProcess.ChildProcess; -function startAppium(outputDir: string, port: string) { +function startAppium(outputDir: string, binary: Binary, port: string) { logger.info('Starting appium server'); - appiumProcess = childProcess.spawn(path.join(outputDir, 'appium', + appiumProcess = childProcess.spawn(path.join(outputDir, binary.filename(), 'node_modules', '.bin', 'appium'), port ? ['--port', port] : []); } diff --git a/lib/cmds/update.ts b/lib/cmds/update.ts index 47e48c5c..6eeb7244 100644 --- a/lib/cmds/update.ts +++ b/lib/cmds/update.ts @@ -10,10 +10,10 @@ import * as rimraf from 'rimraf'; import {Opts} from './opts'; import * as Opt from './'; import {Config} from '../config'; -import {Binary, ChromeDriver, IEDriver, AndroidSDK, StandAlone} from '../binaries'; +import {Binary, ChromeDriver, IEDriver, AndroidSDK, Appium, StandAlone} from '../binaries'; import {FileManager, Downloader} from '../files'; import {Logger, Options, Program} from '../cli'; -import {android as initializeAndroid} from './initialize'; +import {android as initializeAndroid, iOS as checkIOS} from './initialize'; let logger = new Logger('update'); let prog = new Program() @@ -30,6 +30,10 @@ let prog = new Program() .addOption(Opts[Opt.ANDROID_ABIS]) .addOption(Opts[Opt.ANDROID_ACCEPT_LICENSES]); +if (os.type() === 'Darwin') { + prog.addOption(Opts[Opt.IOS]); +} + if (os.type() === 'Windows_NT') { prog.addOption(Opts[Opt.IE]).addOption(Opts[Opt.IE32]); } @@ -37,6 +41,7 @@ if (os.type() === 'Windows_NT') { prog .addOption(Opts[Opt.VERSIONS_STANDALONE]) .addOption(Opts[Opt.VERSIONS_CHROME]) + .addOption(Opts[Opt.VERSIONS_APPIUM]) .addOption(Opts[Opt.VERSIONS_ANDROID]); if (os.type() === 'Windows_NT') { @@ -69,6 +74,10 @@ function update(options: Options): void { ie32 = options[Opt.IE32].getBoolean(); } let android: boolean = options[Opt.ANDROID].getBoolean(); + let ios: boolean = false; + if (options[Opt.IOS]) { + ios = options[Opt.IOS].getBoolean(); + } let outputDir = Config.getSeleniumDir(); let android_api_levels: string[] = options[Opt.ANDROID_API_LEVELS].getString().split(','); let android_abis: string[] = options[Opt.ANDROID_ABIS].getString().split(','); @@ -92,6 +101,7 @@ function update(options: Options): void { binaries[IEDriver.id].versionCustom = options[Opt.VERSIONS_IE].getString(); } binaries[AndroidSDK.id].versionCustom = options[Opt.VERSIONS_ANDROID].getString(); + binaries[Appium.id].versionCustom = options[Opt.VERSIONS_APPIUM].getString(); // if the file has not been completely downloaded, download it // else if the file has already been downloaded, unzip the file, rename it, and give it permissions @@ -129,7 +139,12 @@ function update(options: Options): void { ))), android_api_levels, android_abis, android_accept_licenses, binaries[AndroidSDK.id].versionCustom, logger); }); - installAppium(outputDir); + } + if (ios) { + checkIOS(logger); + } + if (android || ios) { + installAppium(binaries[Appium.id], outputDir); } } @@ -192,15 +207,16 @@ function unzip(binary: T, outputDir: string, fileName: string) } } -function installAppium(outputDir: string): void { +function installAppium(binary: Binary, outputDir: string): void { logger.info('appium: installing appium'); - let folder = path.join(outputDir, 'appium'); + let folder = path.join(outputDir, binary.filename()); try { rimraf.sync(folder); } catch(err) {} fs.mkdirSync(folder); fs.writeFileSync(path.join(folder, 'package.json'), '{}'); - child_process.spawn('npm', ['install', 'appium'], {cwd: folder}); + child_process.spawn('npm', ['install', 'appium@' + binary.version()], {cwd: + folder}); } diff --git a/lib/config.ts b/lib/config.ts index 88dffc1c..68a4ecfc 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -11,6 +11,7 @@ export interface ConfigFile { chrome?: string; ie?: string; android?: string; + appium?: string; } /** @@ -62,6 +63,7 @@ export class Config { configVersions.chrome = configFile.webdriverVersions.chromedriver; configVersions.ie = configFile.webdriverVersions.iedriver; configVersions.android = configFile.webdriverVersions.androidsdk; + configVersions.appium = configFile.webdriverVersions.appium; return configVersions; } diff --git a/lib/files/file_manager.ts b/lib/files/file_manager.ts index 1a327e43..24b82694 100644 --- a/lib/files/file_manager.ts +++ b/lib/files/file_manager.ts @@ -4,7 +4,8 @@ import * as fs from 'fs'; import * as q from 'q'; import * as rimraf from 'rimraf'; -import {Binary, BinaryMap, ChromeDriver, IEDriver, AndroidSDK, StandAlone, OS} from '../binaries'; +import {Binary, BinaryMap, ChromeDriver, IEDriver, AndroidSDK, Appium, StandAlone, OS} from + '../binaries'; import {DownloadedBinary} from './downloaded_binary'; import {Downloader} from './downloader'; import {Logger} from '../cli'; @@ -59,6 +60,9 @@ export class FileManager { if (FileManager.checkOS_(osType, AndroidSDK)) { binaries[AndroidSDK.id] = new AndroidSDK(); } + if (FileManager.checkOS_(osType, Appium)) { + binaries[Appium.id] = new Appium(); + } return binaries; } @@ -213,8 +217,5 @@ export class FileManager { } } }) - try { - rimraf.sync(path.join(outputDir, 'appium')); - } catch (e) {} } } diff --git a/mobile.md b/mobile.md index e368dc88..294c3e41 100644 --- a/mobile.md +++ b/mobile.md @@ -2,15 +2,15 @@ Mobile Browser Support ====================== Support for mobile browsers is provided via [appium](https://github.com/appium/appium). If you -have the Android SDK installed, when you run `webdriver-manager start`, appium will automatically -start on the port specified by `--appium-port`. +have used `webdriver-manager update --android` or `webdriver-manager update --ios`, when you run +`webdriver-manager start`, Appium will automatically start on the port specified by `--appium-port`. Android SDK ----------- `webdriver-manager` will not install the android SDK by default. If you want to test on android, -run `webdriver-manager update --android`. This will download the android SDK, appium, and set up +run `webdriver-manager update --android`. This will download the android SDK, Appium, and set up some virtual android devices for you to run tests against. By default, this will create only an android device running version 24 on x86-64. If you need a different device, you must use the `--android-api-levels` and `--android-abis` flags. So you might run a command like this: @@ -31,7 +31,7 @@ As a practical matter, if you don't want to manually accept the license agreemen `--android-accept-licenses`, which will accept them on your behalf. Once you have installed the Android SDK with the virtual devices you need, use -`webdriver-manager start --android` to boot up appium and begin emulating your android device(s). +`webdriver-manager start --android` to boot up Appium and begin emulating your android device(s). By default `webdriver-manager` will emulate all available android devices. If you would rather emulate a specific device, use `--avds`. So you might use: @@ -44,3 +44,17 @@ If you would prefer not to emulate any android virtual devices, use `--avds none If you need to specify the ports used by the android virtual devices, use `--avd_port`. The port you specify will be used for the console of the first device, and the port one higher will be used for its ADB. The second device will use the next two ports, and so on. + + +iOS +--------- + +When you run `webdriver-manager update --ios`, `webdriver-manager` will install Appium and check +your computer for iOS simulation capabilities. `webdriver-manager` cannot download the xcode +commandline tools for you however, nor can it agree to Apple's user agreement. The xcode +commandline tools come with several virtual devices pre-setup. If you need more, run +`xcrun simctl` for help doing that. + +Once you have installed Appium, `webdriver-manager` will launch it automatically when you run +`webdriver-manager start`. Appium will automatically handle starting iOS device emulation as +needed. diff --git a/spec/files/fileManager_spec.ts b/spec/files/fileManager_spec.ts index 5360ce09..70c319a2 100644 --- a/spec/files/fileManager_spec.ts +++ b/spec/files/fileManager_spec.ts @@ -2,7 +2,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import {Binary, AndroidSDK, ChromeDriver, IEDriver, StandAlone} from '../../lib/binaries'; +import {Binary, AndroidSDK, ChromeDriver, IEDriver, Appium, StandAlone} from '../../lib/binaries'; import {DownloadedBinary, FileManager} from '../../lib/files'; @@ -16,6 +16,7 @@ describe('file manager', () => { expect(FileManager.checkOS_(osType, IEDriver)).toBe(true); expect(FileManager.checkOS_(osType, StandAlone)).toBe(true); expect(FileManager.checkOS_(osType, AndroidSDK)).toBe(true); + expect(FileManager.checkOS_(osType, Appium)).toBe(true); }); it('should return the binary array', () => { @@ -24,6 +25,7 @@ describe('file manager', () => { expect(binaries[ChromeDriver.id].name).toBe((new ChromeDriver()).name); expect(binaries[IEDriver.id].name).toBe((new IEDriver()).name); expect(binaries[AndroidSDK.id].name).toBe((new AndroidSDK()).name); + expect(binaries[Appium.id].name).toBe((new Appium()).name); }); }); @@ -35,6 +37,7 @@ describe('file manager', () => { expect(FileManager.checkOS_(osType, IEDriver)).toBe(false); expect(FileManager.checkOS_(osType, StandAlone)).toBe(true); expect(FileManager.checkOS_(osType, AndroidSDK)).toBe(true); + expect(FileManager.checkOS_(osType, Appium)).toBe(true); }); it('should return the binary array', () => { @@ -42,6 +45,7 @@ describe('file manager', () => { expect(binaries[StandAlone.id].name).toBe((new StandAlone()).name); expect(binaries[ChromeDriver.id].name).toBe((new ChromeDriver()).name); expect(binaries[AndroidSDK.id].name).toBe((new AndroidSDK()).name); + expect(binaries[Appium.id].name).toBe((new Appium()).name); expect(binaries[IEDriver.id]).toBeUndefined(); }); }); @@ -54,6 +58,7 @@ describe('file manager', () => { expect(FileManager.checkOS_(osType, IEDriver)).toBe(false); expect(FileManager.checkOS_(osType, StandAlone)).toBe(true); expect(FileManager.checkOS_(osType, AndroidSDK)).toBe(true); + expect(FileManager.checkOS_(osType, Appium)).toBe(true); }); it('should return the binary array', () => { @@ -62,6 +67,7 @@ describe('file manager', () => { expect(binaries[ChromeDriver.id].name).toBe((new ChromeDriver()).name); expect(binaries[IEDriver.id]).toBeUndefined(); expect(binaries[AndroidSDK.id].name).toBe((new AndroidSDK()).name); + expect(binaries[Appium.id].name).toBe((new Appium()).name); }); }); @@ -70,6 +76,7 @@ describe('file manager', () => { let selenium = new StandAlone(); let chrome = new ChromeDriver(); let android = new AndroidSDK(); + let appium = new Appium(); let ie = new IEDriver(); let ostype: string; let arch: string; @@ -88,6 +95,7 @@ describe('file manager', () => { existingFiles.push(android.prefix() + '24.1.0' + android.executableSuffix()); existingFiles.push(android.prefix() + '24.1.1' + android.suffix(ostype)); existingFiles.push(android.prefix() + '24.1.1' + android.executableSuffix()); + existingFiles.push(appium.prefix() + '1.5.3' + appium.suffix(ostype)); if (ostype == 'Windows_NT') { existingFiles.push(ie.prefix() + '_Win32_2.51.0' + ie.suffix()); existingFiles.push(ie.prefix() + '_Win32_2.51.0' + ie.executableSuffix(ostype)); @@ -172,6 +180,27 @@ describe('file manager', () => { }); }); + describe('versions for appium', () => { + it('should find the correct version for windows', () => { + setup('Windows_NT'); + let downloaded = FileManager.downloadedVersions_(appium, ostype, arch, existingFiles); + expect(downloaded.versions.length).toBe(1); + expect(downloaded.versions[0]).toBe('1.5.3'); + }); + it('should find the correct version for mac', () => { + setup('Darwin'); + let downloaded = FileManager.downloadedVersions_(appium, ostype, arch, existingFiles); + expect(downloaded.versions.length).toBe(1); + expect(downloaded.versions[0]).toBe('1.5.3'); + }); + it('should find the correct version for linux', () => { + setup('Linux'); + let downloaded = FileManager.downloadedVersions_(appium, ostype, arch, existingFiles); + expect(downloaded.versions.length).toBe(1); + expect(downloaded.versions[0]).toBe('1.5.3'); + }); + }); + describe('versions for ie on windows', () => { it('should find the correct version for windows', () => { setup('Windows_NT');