From f56a6a70bbd551859ef2a6ac11802b4eefbd6dac Mon Sep 17 00:00:00 2001 From: tborys Date: Thu, 20 Jun 2013 17:44:23 +0100 Subject: [PATCH] Decoupling installation and removal from launching the app for iOS by adding new end points to appium server. --- .gitmodules | 3 + app/controller.js | 121 +++++++++++++++++ app/helpers.js | 17 +++ app/routing.js | 5 + reset.sh | 8 ++ .../main/java/com/example/LocalWebDriver.java | 124 ++++++++++++++++++ 6 files changed, 278 insertions(+) create mode 100644 sample-code/examples/java/testng/src/main/java/com/example/LocalWebDriver.java diff --git a/.gitmodules b/.gitmodules index 37e981abb..a1b3aefee 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "submodules/selendroid"] path = submodules/selendroid url = https://github.com/DominikDary/selendroid.git + [submodule "submodules/fruitstrap"] + path = submodules/fruitstrap + url = https://github.com/tborys/fruitstrap.git \ No newline at end of file diff --git a/app/controller.js b/app/controller.js index ddbc90c53..beef1ff9f 100644 --- a/app/controller.js +++ b/app/controller.js @@ -8,6 +8,7 @@ var status = require('./uiauto/lib/status') , path = require('path') , version = require('../package.json').version , getGitRev = require('./helpers.js').getGitRev + , helpers = require('./helpers') , _ = require('underscore'); function getResponseHandler(req, res) { @@ -152,6 +153,126 @@ exports.getStatus = function(req, res) { respondSuccess(req, res, data); }; +exports.installApp = function(req, res){ + if(req.appium.args.udid !== null){ + if(req.appium.args.udid.length === 40){ + req.appium.deviceType = "ios"; + } + unpackApp(req, function(unpackedAppPath){ + if(unpackedAppPath === null){ + req.appium.deviceType = null; + respondError(req, res, 'Only a (zipped) app files can be installed using this endpoint'); + } else { + installAppOnDeviceThroughCmdLine(req.appium.deviceType, req.appium.args.udid, unpackedAppPath, function(code, message){ + if(code === 0) { + respondSuccess(req, res, message); + } else { + respondError(req, res, message); + } + req.appium.deviceType = null; + }); + } + }); + } else { + respondSuccess(req, res, 'No udid was provided, therefore the app [' + req.body.appPath + '] was not installed'); + } +}; + +var unpackApp = function(req, cb) { + var reqAppPath = req.body.appPath; + if (reqAppPath.substring(0, 4) === "http") { + req.appium.downloadAndUnzipApp(reqAppPath, function(err, appPath) { + cb(appPath); + }); + } else if (reqAppPath.substring(reqAppPath.length - 4) === ".zip") { + req.appium.unzipLocalApp(reqAppPath, function(err, appPath) { + cb(appPath); + }); + } else if (reqAppPath.substring(reqAppPath.length - 4) === ".app") { + cb(reqAppPath.toString()); + } else { + cb(null); + } +}; + +var installAppOnDeviceThroughCmdLine = function(deviceType, udid, unzippedAppPath, cb) { + var installationCommand = null; + if (deviceType === "ios") { + installationCommand = './submodules/fruitstrap/fruitstrap install --id ' + udid + ' --bundle ' + unzippedAppPath; + } else { + installationCommand = 'ADB -s ' + udid + ' install ' + unzippedAppPath; + } + helpers.executeTerminalCommand(installationCommand, function(code) { + if(code === 0) { + cb(code, 'Successfully unzipped and installed [' + unzippedAppPath + '] to device with id [' + udid + ']'); + } else { + cb(code, 'Unable to install [' + unzippedAppPath + '] to device with id [' + udid + '].'); + } + }); +}; + +exports.unInstallApp = function(req, res) { + if(req.appium.args.udid !== null) { + if(req.appium.args.udid.length === 40){ + req.appium.deviceType = "ios"; + } + removeAppFromDeviceThroughCmdLine(req.appium.deviceType, req.appium.args.udid, req.body.bundleId, function(code, message){ + if(code === 0) { + respondSuccess(req, res, message); + } else { + respondError(req, res, message); + } + }); + } else { + respondSuccess(req, res, 'No udid was provided, therefore the app [' + req.body.bundleId + '] was not removed'); + } +}; + +var removeAppFromDeviceThroughCmdLine = function(deviceType, udid, bundleId, cb) { + var removeCommand = null; + if (deviceType === "ios") { + removeCommand = './submodules/fruitstrap/fruitstrap uninstall --id ' + udid + ' --bundle ' + bundleId; + } else { + removeCommand = 'ADB -s ' + udid + ' uninstall ' + bundleId; + } + helpers.executeTerminalCommand(removeCommand, function(code) { + if(code === 0) { + cb(code, 'Successfully un-installed [' + bundleId + '] from device with id [' + udid + ']'); + } else { + cb(code, 'Unable to un-install [' + bundleId + '] from device with id [' + udid + '].'); + } + }); +}; + +exports.isAppInstalled = function(req, res) { + if(req.appium.args.udid !== null) { + if(req.appium.args.udid.length === 40){ + req.appium.deviceType = "ios"; + } + checkIfAppIsInstalledThroughCmdLine(req.appium.deviceType, req.appium.args.udid, req.body.bundleId, function(code, message){ + if(code === 0) { + respondSuccess(req, res, true); + } else { + respondSuccess(req, res, false); + } + }); + } else { + respondSuccess(req, res, 'No udid was provided, therefore no check was done for app [' + req.body.bundleId + ']'); + } +}; + +var checkIfAppIsInstalledThroughCmdLine = function(deviceType, udid, bundleId, cb) { + var isInstalledCommand = null; + if (deviceType === "ios") { + isInstalledCommand = './submodules/fruitstrap/fruitstrap isInstalled --id ' + udid + ' --bundle ' + bundleId; + } else { + isInstalledCommand = 'ADB -s ' + udid + ' uninstall ' + bundleId; //adb shell pm path com.authorwjf.web_view + } + helpers.executeTerminalCommand(isInstalledCommand, function(code) { + cb(code); + }); +}; + exports.createSession = function(req, res) { if (typeof req.body === 'string') { req.body = JSON.parse(req.body); diff --git a/app/helpers.js b/app/helpers.js index 652e87e65..f175d212c 100644 --- a/app/helpers.js +++ b/app/helpers.js @@ -298,6 +298,23 @@ exports.parseWebCookies = function(cookieStr) { return cookies; }; +exports.executeTerminalCommand = function(command, cb) { + var terminal = require('child_process').spawn('bash'); + + terminal.stdout.on('data', function (data) { + logger.info('stdout: ' + data); + }); + + terminal.on('exit', function (code) { + logger.info('child process exited with code ' + code); + cb(code); + }); + + logger.info('Sending command to terminal to terminal [' + command + ']'); + terminal.stdin.write(command + '\n'); + terminal.stdin.end(); +}; + exports.rotateImage = function(imgPath, deg, cb) { logger.info("Rotating image " + imgPath + " " + deg + " degrees"); var scriptPath = path.resolve(__dirname, "uiauto/Rotate.applescript"); diff --git a/app/routing.js b/app/routing.js index ef3e139ea..a0a148d83 100644 --- a/app/routing.js +++ b/app/routing.js @@ -151,6 +151,11 @@ module.exports = function(appium) { rest.post('/wd/hub/session/:sessionId/touch/flick_precise', controller.mobileFlick); rest.post('/wd/hub/session/:sessionId/touch/swipe', controller.mobileSwipe); + // these are for installation/removal of iOS app. + rest.post('/wd/hub/app/install', controller.installApp); + rest.post('/wd/hub/app/uninstall', controller.unInstallApp); + rest.post('/wd/hub/app/installed', controller.isAppInstalled); + // keep this at the very end! rest.all('/*', controller.unknownCommand); //console.log(rest.routes.get); diff --git a/reset.sh b/reset.sh index 6967160a0..7a7c510e6 100755 --- a/reset.sh +++ b/reset.sh @@ -191,6 +191,13 @@ reset_selendroid() { run_cmd $grunt setConfigVer:selendroid } +make_fruitstrap() { + echo "CONFIGURING FRUITSTRAP" + echo "* making fruitstrap" + run_cmd cd $appium_home/submodules/fruitstrap/ + run_cmd make fruitstrap +} + cleanup() { echo "CLEANING UP" echo "* Cleaning any temp files" @@ -216,6 +223,7 @@ main() { if $should_reset_selendroid ; then reset_selendroid fi + make_fruitstrap cleanup reset_successful=true } diff --git a/sample-code/examples/java/testng/src/main/java/com/example/LocalWebDriver.java b/sample-code/examples/java/testng/src/main/java/com/example/LocalWebDriver.java new file mode 100644 index 000000000..8b907af2d --- /dev/null +++ b/sample-code/examples/java/testng/src/main/java/com/example/LocalWebDriver.java @@ -0,0 +1,124 @@ +package com.example; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import org.openqa.selenium.remote.Command; +import org.openqa.selenium.remote.CommandInfo; +import org.openqa.selenium.remote.HttpCommandExecutor; +import org.openqa.selenium.remote.HttpVerb; +import org.openqa.selenium.remote.Response; +import org.openqa.selenium.remote.SessionId; + +import com.qa.framework.utils.reporter.Reporter; + +public class LocalWebDriver { + + //Set constants + private HttpCommandExecutor executor = null; + private URL baseURL = null; + private final String INSTALLAPPCOMMAND = "INSTALL_APP"; + private final String UNINSTALLAPPCOMMAND = "UNINSTALL_APP"; + private final String ISAPPINSTALLEDCOMMAND = "IS_APP_INSTALLED"; + /** + * CONSTRUCTOR: + * + * @param configurationFilePath the path to the file containing all the config values + * @throws MalformedURLException + */ + public LocalWebDriver() { + + try { + //set the base url for the executor. + this.baseURL = new URL("http://127.0.0.1:4444/wd/hub"); + } catch (MalformedURLException e) { + Reporter.error("Unable to create url [" + e.getMessage() + "]"); + } + //configure the end points which we can call. + Map additionalCommands = new HashMap(); + additionalCommands.put(INSTALLAPPCOMMAND, new CommandInfo("/app/install/", HttpVerb.POST)); + additionalCommands.put(ISAPPINSTALLEDCOMMAND, new CommandInfo("/app/installed/", HttpVerb.POST)); + additionalCommands.put(UNINSTALLAPPCOMMAND, new CommandInfo("/app/uninstall/", HttpVerb.POST)); + + executor = new HttpCommandExecutor(additionalCommands, this.baseURL); + } + + /** + * This method will use the appPath parameter to install the app on the attached + * iOS device. Please make sure the appPath is accessible by the appium server. + * + * @param appPath to a local (zipped) app file or a url for a zipped app file. + */ + public void installApp(String appPath){ + Map parameters = new HashMap(); + parameters.put("appPath", appPath); + Command command = new Command(new SessionId(""), INSTALLAPPCOMMAND, parameters); + try { + Response response = executor.execute(command); + if(response.getStatus() == 0){ + Reporter.info(response.getValue().toString()); + } else { + Reporter.error(response.getValue().toString()); + } + } catch (IOException e) { + Reporter.error("Unable to install app: " + e.getMessage()); + } + } + + /** + * This method will use the bundlId parameter to un-install the app from + * the attached iOS device. + * + * @param bundleId of the app you would like to remove + * @return boolean it will return a true if the app was successfully un-installed and false + * if it could not found or if the un-install failed. + */ + public boolean unInstallApp(String bundleId){ + if (isAppInstalled(bundleId)){ + Map parameters = new HashMap(); + parameters.put("bundleId", bundleId); + Command command = new Command(new SessionId(""), UNINSTALLAPPCOMMAND, parameters); + try { + Response response = executor.execute(command); + if(response.getStatus() == 0){ + Reporter.info(response.getValue().toString()); + return true; + } else { + Reporter.info(response.getValue().toString()); + } + } catch (IOException e) { + Reporter.error("Unable to uninstall app: " + e.getMessage()); + } + } + return false; + } + + /** + * This method will use the bundleId parameter to check if the app is + * installed on the attached iOS device + * + * @param bundleId of the app you would like to check for + * + * @return boolean true if the app is installed and false if its not. + */ + public boolean isAppInstalled(String bundleId){ + Map parameters = new HashMap(); + parameters.put("bundleId", bundleId); + Command command = new Command(new SessionId(""), ISAPPINSTALLEDCOMMAND, parameters); + try { + Response response = executor.execute(command); + if(response.getValue().equals(true)){ + Reporter.info("Found the app with bundleID: " + bundleId); + return true; + } else { + Reporter.info("No app found with bundleID: " + bundleId); + } + } catch (IOException e) { + Reporter.error("Unable to check if app is installed: " + e.getMessage()); + } + return false; + } +}