// Appium webserver controller methods // https://github.com/hugs/appium/blob/master/appium/server.py "use strict"; var status = require('./status.js') , logger = require('./logger.js').get('appium') , _s = require("underscore.string") , swig = require('swig') , path = require('path') , version = require('../../package.json').version , proxy = require('./proxy.js') , responses = require('./responses.js') , getResponseHandler = responses.getResponseHandler , respondError = responses.respondError , respondSuccess = responses.respondSuccess , checkMissingParams = responses.checkMissingParams , notYetImplemented = responses.notYetImplemented , _ = require('underscore'); exports.getGlobalBeforeFilter = function(appium) { return function(req, res, next) { req.appium = appium; req.device = appium.device; logger.debug("Appium request initiated at " + req.url); if (typeof req.body === "object") { logger.debug("Request received with params: " + JSON.stringify(req.body)); } if (proxy.shouldProxy(req)) { proxy.doProxy(req, res, next); } else { next(); } }; }; exports.sessionBeforeFilter = function(req, res, next) { var match = new RegExp("([^/]+)").exec(req.params[0]); var sessId = match ? match[1] : null; // if we don't actually have a valid session, respond with an error if (sessId && (!req.device || req.appium.sessionId != sessId)) { res.send(404, {sessionId: null, status: status.codes.NoSuchDriver.code, value: ''}); } else { next(); } }; exports.getStatus = function(req, res) { // Return a static JSON object to the client var gitSha = req.appium.serverConfig['git-sha']; var data = {build: {version: version}}; if (typeof gitSha !== "undefined") { data.build.revision = gitSha; } respondSuccess(req, res, data); }; exports.installApp = function(req, res) { var install = function(appPath) { req.device.installApp(appPath, function(error, response) { if (error !== null) { respondError(req, res, response); } else { respondSuccess(req, res, response); } }); }; if (typeof req.body.appPath !== "undefined") { req.device.unpackApp(req, function(unpackedAppPath) { if (unpackedAppPath === null) { respondError(req, res, 'Only a (zipped) app/apk files can be installed using this endpoint'); } else { install(unpackedAppPath); } }); } else if (typeof req.appium.args.app !== "undefined") { install(req.appium.args.app); } else { respondError(req, res, "No app defined (either through desired capabilities or as an argument)"); } }; exports.removeApp = function(req, res) { if (checkMissingParams(res, {bundleId: req.body.bundleId}, true)) { req.device.removeApp(req.body.bundleId, function(error, response) { if (error !== null) { respondError(req, res, response); } else { respondSuccess(req, res, response); } }); } }; exports.isAppInstalled = function(req, res) { if (checkMissingParams(res, {bundleId: req.body.bundleId}, true)) { req.device.isAppInstalled(req.body.bundleId, function(error, stdout) { if (error !== null) { respondSuccess(req, res, false); } else { if ((req.appium.args.udid && req.appium.args.udid.length === 40) || (typeof stdout[0] !== "undefined")) { respondSuccess(req, res, true); } else { respondSuccess(req, res, false); } } }); } }; exports.launchApp = function(req, res) { req.device.start(function() { respondSuccess(req, res, "Successfully launched the [" + req.appium.args.app + "] app."); }, function() { respondError(req, res, "Unable to launch the [" + req.appium.args.app + "] app."); }); }; exports.closeApp = function(req, res) { req.device.stop(function() { respondSuccess(req, res, "Successfully closed the [" + req.appium.args.app + "] app."); }, function() { respondError(req, res, "Something whent wront whilst closing the [" + req.appium.args.app + "] app."); }); }; exports.createSession = function(req, res) { if (typeof req.body === 'string') { req.body = JSON.parse(req.body); } var desired = req.body.desiredCapabilities; var next = function(reqHost, sessionId, device) { var redirect = function() { res.set('Location', "http://"+reqHost+"/wd/hub/session/" + sessionId); res.send(303); }; if (desired && desired.newCommandTimeout) { device.setCommandTimeout(desired.newCommandTimeout, redirect); } else { redirect(); } }; if (req.appium.preLaunched && req.appium.sessionId) { req.appium.preLaunched = false; next(req.headers.host, req.appium.sessionId, req.appium.device, true); } else { req.appium.start(req.body.desiredCapabilities, function(err, instance) { if (err) { logger.error("Failed to start an Appium session, err was: " + err); respondError(req, res, status.codes.NoSuchDriver, err); } else { logger.info("Appium session started with sessionId " + req.appium.sessionId); next(req.headers.host, req.appium.sessionId, instance); } }); } }; exports.getSession = function(req, res) { // Return a static JSON object to the client respondSuccess(req, res, req.device.capabilities); }; exports.getSessions = function(req, res) { var sessions = []; if (req.appium.sessionId !== null) { sessions.push({ id: req.appium.sessionId , capabilities: req.device.capabilities }); } respondSuccess(req, res, sessions); }; exports.reset = function(req, res) { req.appium.reset(getResponseHandler(req, res)); }; exports.lock = function(req, res) { var seconds = req.body.seconds; if (checkMissingParams(res, {seconds: seconds})) { req.device.lock(seconds, getResponseHandler(req, res)); } }; exports.background = function(req, res) { var seconds = req.body.seconds; if (checkMissingParams(res, {seconds: seconds})) { req.device.background(seconds, getResponseHandler(req, res)); } }; exports.deleteSession = function(req, res) { req.appium.stop(getResponseHandler(req, res)); }; exports.equalsElement = function(req, res) { var element = req.params.elementId , other = req.params.otherId; req.device.equalsWebElement(element, other, getResponseHandler(req, res)); }; exports.findElements = function(req, res) { var strategy = req.body.using , selector = req.body.value; if (checkMissingParams(res, {strategy: strategy, selector: selector}, true)) { req.device.findElements(strategy, selector, getResponseHandler(req, res)); } }; exports.findElement = function(req, res) { var strategy = req.body.using , selector = req.body.value; if (checkMissingParams(res, {strategy: strategy, selector: selector}, true)) { req.device.findElement(strategy, selector, getResponseHandler(req, res)); } }; exports.findElementFromElement = function(req, res) { var element = req.params.elementId , strategy = req.body.using , selector = req.body.value; req.device.findElementFromElement(element, strategy, selector, getResponseHandler(req, res)); }; exports.findElementsFromElement = function(req, res) { var element = req.params.elementId , strategy = req.body.using , selector = req.body.value; req.device.findElementsFromElement(element, strategy, selector, getResponseHandler(req, res)); }; exports.setValue = function(req, res) { var elementId = req.params.elementId , value = req.body.value.join(''); req.device.setValue(elementId, value, getResponseHandler(req, res)); }; exports.doClick = function(req, res) { var elementId = req.params.elementId || req.body.element; req.device.click(elementId, getResponseHandler(req, res)); }; exports.touchLongClick = function(req, res) { var element = req.body.element; if (checkMissingParams(res, {element: element}, true)) { req.device.touchLongClick(element, getResponseHandler(req, res)); } }; exports.fireEvent = function(req, res) { var elementId = req.body.element , evt = req.body.event; req.device.fireEvent(evt, elementId, getResponseHandler(req, res)); }; exports.mobileTap = function(req, res) { req.body = _.defaults(req.body, { tapCount: 1 , touchCount: 1 , duration: 0.1 , x: 0.5 , y: 0.5 , element: null }); var tapCount = req.body.tapCount , touchCount = req.body.touchCount , duration = req.body.duration , element = req.body.element , x = req.body.x , y = req.body.y; req.device.complexTap(tapCount, touchCount, duration, x, y, element, getResponseHandler(req, res)); }; exports.mobileFlick = function(req, res) { req.body = _.defaults(req.body, { touchCount: 1 , startX: 0.5 , startY: 0.5 , endX: 0.5 , endY: 0.5 , element: null }); var touchCount = req.body.touchCount , element = req.body.element , startX = req.body.startX , startY = req.body.startY , endX = req.body.endX , endY = req.body.endY; req.device.flick(startX, startY, endX, endY, touchCount, element, getResponseHandler(req, res)); }; exports.mobileDrag = function(req, res) { req.body = _.defaults(req.body, { startX: 0.5 , startY: 0.5 , endX: 0.5 , endY: 0.5 , steps: 40 , element: null , destEl: null }); var element = req.body.element , destEl = req.body.destEl , startX = req.body.startX , startY = req.body.startY , endX = req.body.endX , endY = req.body.endY , steps = req.body.steps; req.device.drag(startX, startY, endX, endY, steps, element, destEl, getResponseHandler(req, res)); }; exports.mobileSource = function(req, res) { var type = req.body.type; if (checkMissingParams(res, {type: type})) { if (type.toLowerCase() === "xml") { req.device.getPageSourceXML(getResponseHandler(req, res)); } else { req.device.getPageSource(getResponseHandler(req, res)); } } }; exports.find = function(req, res) { var strategy = "dynamic" , selector = req.body , all = selector && selector[0] && typeof selector[0] === "string" && selector[0].toLowerCase() === "all"; if (all) { req.device.findElements(strategy, selector, getResponseHandler(req, res)); } else { req.device.findElement(strategy, selector, getResponseHandler(req, res)); } }; exports.mobileSwipe = function(req, res) { req.body = _.defaults(req.body, { touchCount: 1 , startX: 0.5 , startY: 0.5 , endX: 0.5 , endY: 0.5 , duration: 0.8 , element: null }); var touchCount = req.body.touchCount , element = req.body.element , duration = req.body.duration , startX = req.body.startX , startY = req.body.startY , endX = req.body.endX , endY = req.body.endY; req.device.swipe(startX, startY, endX, endY, duration, touchCount, element, getResponseHandler(req, res)); }; exports.mobileRotation = function(req, res) { req.body = _.defaults(req.body, { x: 0.5 , y: 0.5 , radius: 0.5 , rotation: 3.14159265359 , touchCount: 2 , duration: 1 , element: null }); var element = req.body.element , duration = req.body.duration , x = req.body.x , y = req.body.y , radius = req.body.radius , touchCount = req.body.touchCount , rotation = req.body.rotation; req.device.rotate(x, y, radius, rotation, duration, touchCount, element, getResponseHandler(req, res)); }; exports.mobilePinchClose = function(req, res) { req.body = _.defaults(req.body, { startX: 0.5 , startY: 0.5 , endX: 0.5 , endY: 0.5 , duration: 0.8 , percent: 200 , steps: 50 , element: null }); var element = req.body.element , duration = req.body.duration , startX = req.body.startX , startY = req.body.startY , endX = req.body.endX , endY = req.body.endY , percent = req.body.percent , steps = req.body.steps; req.device.pinchClose(startX, startY, endX, endY, duration, percent, steps, element, getResponseHandler(req, res)); }; exports.mobilePinchOpen = function(req, res) { req.body = _.defaults(req.body, { startX: 0.5 , startY: 0.5 , endX: 0.5 , endY: 0.5 , duration: 0.8 , percent: 200 , steps: 50 , element: null }); var element = req.body.element , duration = req.body.duration , startX = req.body.startX , startY = req.body.startY , endX = req.body.endX , endY = req.body.endY , percent = req.body.percent , steps = req.body.steps; req.device.pinchOpen(startX, startY, endX, endY, duration, percent, steps, element, getResponseHandler(req, res)); }; exports.mobileScrollTo = function(req, res) { req.body = _.defaults(req.body, { element: null , text: null }); var element = req.body.element , text = req.body.text; req.device.scrollTo(element, text, getResponseHandler(req, res)); }; exports.mobileShake = function(req, res) { req.device.shake(getResponseHandler(req, res)); }; exports.mobileSetLocation = function(req, res) { req.body = _.defaults(req.body, { latitude: null , longitude: null , altitude: null , horizontalAccuracy: null , verticalAccuracy: null , course: null , speed: null }); var latitude = req.body.latitude , longitude = req.body.longitude , altitude = req.body.altitude , horizontalAccuracy = req.body.horizontalAccuracy , verticalAccuracy = req.body.verticalAccuracy , course = req.body.course , speed = req.body.speed; if (checkMissingParams(res, {latitude: latitude, longitude: longitude})) { req.device.setLocation(latitude, longitude, altitude, horizontalAccuracy, verticalAccuracy, course, speed, getResponseHandler(req, res)); } }; exports.hideKeyboard = function(req, res) { var keyName = req.body.keyName; req.device.hideKeyboard(keyName, getResponseHandler(req, res)); }; exports.clear = function(req, res) { var elementId = req.params.elementId; req.device.clear(elementId, getResponseHandler(req, res)); }; exports.getText = function(req, res) { var elementId = req.params.elementId; req.device.getText(elementId, getResponseHandler(req, res)); }; exports.getName = function(req, res) { var elementId = req.params.elementId; req.device.getName(elementId, getResponseHandler(req, res)); }; exports.getAttribute = function(req, res) { var elementId = req.params.elementId , attributeName = req.params.name; req.device.getAttribute(elementId, attributeName, getResponseHandler(req, res)); }; exports.getCssProperty = function(req, res) { var elementId = req.params.elementId , propertyName = req.params.propertyName; req.device.getCssProperty(elementId, propertyName, getResponseHandler(req, res)); }; exports.getLocation = function(req, res) { var elementId = req.params.elementId; req.device.getLocation(elementId, getResponseHandler(req, res)); }; exports.getSize = function(req, res) { var elementId = req.params.elementId; req.device.getSize(elementId, getResponseHandler(req, res)); }; exports.getWindowSize = function(req, res) { var windowHandle = req.params.windowhandle; req.device.getWindowSize(windowHandle, getResponseHandler(req, res)); }; exports.getPageIndex = function(req, res) { var elementId = req.params.elementId; req.device.getPageIndex(elementId, getResponseHandler(req, res)); }; exports.keyevent = function(req, res) { req.body = _.defaults(req.body, { keycode: null , metastate: null }); var keycode = req.body.keycode , metastate = req.body.metastate; req.device.keyevent(keycode, metastate, getResponseHandler(req, res)); }; exports.back = function(req, res) { req.device.back(getResponseHandler(req, res)); }; exports.forward = function(req, res) { req.device.forward(getResponseHandler(req, res)); }; exports.refresh = function(req, res) { req.device.refresh(getResponseHandler(req, res)); }; exports.keys = function(req, res) { var keys = req.body.value.join(''); req.device.keys(keys, getResponseHandler(req, res)); }; exports.frame = function(req, res) { var frame = req.body.id; req.device.frame(frame, getResponseHandler(req, res)); }; exports.leaveWebView = function(req, res) { req.device.leaveWebView(getResponseHandler(req, res)); }; exports.elementDisplayed = function(req, res) { var elementId = req.params.elementId; req.device.elementDisplayed(elementId, getResponseHandler(req, res)); }; exports.elementEnabled = function(req, res) { var elementId = req.params.elementId; req.device.elementEnabled(elementId, getResponseHandler(req, res)); }; exports.elementSelected = function(req, res) { var elementId = req.params.elementId; req.device.elementSelected(elementId, getResponseHandler(req, res)); }; exports.getPageSource = function(req, res) { req.device.getPageSource(getResponseHandler(req, res)); }; exports.waitForPageLoad = function(req, res) { req.body = _.defaults(req.body, { timeout: 30 }); var timeout = req.body.timeout; req.device.waitForPageLoad(timeout, getResponseHandler(req, res)); }; exports.getAlertText = function(req, res) { req.device.getAlertText(getResponseHandler(req, res)); }; exports.setAlertText = function(req, res) { var text = req.body.text; req.device.setAlertText(text, getResponseHandler(req, res)); }; exports.postAcceptAlert = function(req, res) { req.device.postAcceptAlert(getResponseHandler(req, res)); }; exports.postDismissAlert = function(req, res) { req.device.postDismissAlert(getResponseHandler(req, res)); }; exports.implicitWait = function(req, res) { var ms = req.body.ms; req.device.implicitWait(ms, getResponseHandler(req, res)); }; exports.asyncScriptTimeout = function(req, res) { var ms = req.body.ms; req.device.asyncScriptTimeout(ms, getResponseHandler(req, res)); }; exports.setOrientation = function(req, res) { var orientation = req.body.orientation; req.device.setOrientation(orientation, getResponseHandler(req, res)); }; exports.setLocation = function(req, res) { var latitude = req.body.latitude , longitude = req.body.longitude , altitude = req.body.altitude; req.device.setLocation(latitude, longitude, altitude, null, null, null, null, getResponseHandler(req, res)); }; exports.getOrientation = function(req, res) { req.device.getOrientation(getResponseHandler(req, res)); }; exports.getScreenshot = function(req, res) { req.device.getScreenshot(getResponseHandler(req, res)); }; exports.moveTo = function(req, res) { req.body = _.defaults(req.body, { xoffset: 0.5 , yoffset: 0.5 }); var xoffset = req.body.xoffset , yoffset = req.body.yoffset , element = req.body.element; req.device.moveTo(element, xoffset, yoffset, getResponseHandler(req, res)); }; exports.clickCurrent = function(req, res) { var button = req.body.button || 0; req.device.clickCurrent(button, getResponseHandler(req, res)); }; exports.pickAFlickMethod = function(req, res) { if (typeof req.body.xSpeed !== "undefined" || typeof req.body.xspeed !== "undefined") { exports.flick(req, res); } else { exports.flickElement(req, res); } }; exports.flick = function(req, res) { var swipe = req.body.swipe , xSpeed = req.body.xSpeed , ySpeed = req.body.ySpeed , element = req.body.element; if (typeof xSpeed === "undefined") { xSpeed = req.body.xspeed; } if (typeof ySpeed === "undefined") { ySpeed = req.body.yspeed; } if (checkMissingParams(res, {xSpeed: xSpeed, ySpeed: ySpeed})) { if (element) { exports.flickElement(req, res); } else { req.device.fakeFlick(xSpeed, ySpeed, swipe, getResponseHandler(req, res)); } } }; exports.flickElement = function(req, res) { var element = req.body.element , xoffset = req.body.xoffset , yoffset = req.body.yoffset , speed = req.body.speed; if (checkMissingParams(res, {element: element, xoffset: xoffset, yoffset: yoffset})) { req.device.fakeFlickElement(element, xoffset, yoffset, speed, getResponseHandler(req, res)); } }; exports.execute = function(req, res) { var script = req.body.script , args = req.body.args; if (checkMissingParams(res, {script: script, args: args})) { if (_s.startsWith(script, "mobile: ")) { var realCmd = script.replace("mobile: ", ""); exports.executeMobileMethod(req, res, realCmd); } else { req.device.execute(script, args, getResponseHandler(req, res)); } } }; exports.executeAsync = function(req, res) { var script = req.body.script , args = req.body.args , responseUrl = ''; responseUrl += 'http://' + req.appium.args.address + ':' + req.appium.args.port; responseUrl += '/wd/hub/session/' + req.appium.sessionId + '/receive_async_response'; if (checkMissingParams(res, {script: script, args: args})) { req.device.executeAsync(script, args, responseUrl, getResponseHandler(req, res)); } }; exports.executeMobileMethod = function(req, res, cmd) { var args = req.body.args , params = {}; if (args.length) { if (args.length !== 1) { res.send(400, "Mobile methods only take one parameter, which is a " + "hash of named parameters to send to the method"); } else { params = args[0]; } } if (_.has(mobileCmdMap, cmd)) { req.body = params; mobileCmdMap[cmd](req, res); } else { logger.info("Tried to execute non-existent mobile command '"+cmd+"'"); notYetImplemented(req, res); } }; exports.title = function(req, res) { req.device.title(getResponseHandler(req, res)); }; exports.submit = function(req, res) { var elementId = req.params.elementId; req.device.submit(elementId, getResponseHandler(req, res)); }; exports.postUrl = function(req, res) { var url = req.body.url; if (checkMissingParams(res, {url: url})) { req.device.url(url, getResponseHandler(req, res)); } }; exports.getUrl = function(req, res) { req.device.getUrl(getResponseHandler(req, res)); }; exports.active = function(req, res) { req.device.active(getResponseHandler(req, res)); }; exports.getWindowHandle = function(req, res) { req.device.getWindowHandle(getResponseHandler(req, res)); }; exports.setWindow = function(req, res) { var name = req.body.name; if (checkMissingParams(res, {name: name})) { req.device.setWindow(name, getResponseHandler(req, res)); } }; exports.closeWindow = function(req, res) { req.device.closeWindow(getResponseHandler(req, res)); }; exports.getWindowHandles = function(req, res) { req.device.getWindowHandles(getResponseHandler(req, res)); }; exports.setCommandTimeout = function(req, res) { var timeout = req.body.timeout; if (checkMissingParams(res, {timeout: timeout})) { timeout = parseInt(timeout, 10); req.device.setCommandTimeout(timeout, getResponseHandler(req, res)); } }; exports.getCommandTimeout = function(req, res) { req.device.getCommandTimeout(getResponseHandler(req, res)); }; exports.receiveAsyncResponse = function(req, res) { var asyncResponse = req.body; req.device.receiveAsyncResponse(asyncResponse); res.send(200, 'OK'); }; exports.setValueImmediate = function(req, res) { var element = req.body.element , value = req.body.value; if (checkMissingParams(res, {element: element, value: value})) { req.device.setValueImmediate(element, value, getResponseHandler(req, res)); } }; exports.findAndAct = function(req, res) { var params = { strategy: req.body.strategy , selector: req.body.selector , index: req.body.index , action: req.body.action , actionParams: req.body.params }; if (typeof params.action === "undefined") { params.action = "tap"; } if (typeof params.index === "undefined") { params.index = 0; } params.index = parseInt(params.index, 10); if (typeof params.actionParams === "undefined") { params.actionParams = []; } if (checkMissingParams(res, params)) { req.device.findAndAct(params.strategy, params.selector, params.index, params.action, params.actionParams, getResponseHandler(req, res)); } }; exports.getCookies = function(req, res) { req.device.getCookies(getResponseHandler(req, res)); }; exports.setCookie = function(req, res) { var cookie = req.body.cookie; if (checkMissingParams(res, {cookie: cookie})) { if (typeof cookie.name !== "string" || typeof cookie.value !== "string") { return respondError(req, res, status.codes.UnknownError, "setCookie requires cookie of form {name: 'xxx', value: 'yyy'}"); } req.device.setCookie(cookie, getResponseHandler(req, res)); } }; exports.deleteCookie = function(req, res) { var cookie = req.params.name; req.device.deleteCookie(cookie, getResponseHandler(req, res)); }; exports.deleteCookies = function(req, res) { req.device.deleteCookies(getResponseHandler(req, res)); }; exports.getCurrentActivity = function(req, res) { req.device.getCurrentActivity(getResponseHandler(req, res)); }; // mobile :findElementNameContains, name: 'sign in' exports.findElementNameContains = function(req, res) { var name = req.body.name; if (checkMissingParams(res, {name: name})) { req.device.findElementNameContains(name, getResponseHandler(req, res)); } }; exports.getLog = function(req, res) { var logType = req.body.type; if (checkMissingParams(res, {logType: logType})) { req.device.getLog(logType, getResponseHandler(req, res)); } }; exports.getLogTypes = function(req, res) { req.device.getLogTypes(getResponseHandler(req, res)); }; exports.getStrings = function(req, res) { req.device.getStrings(getResponseHandler(req, res)); }; exports.unknownCommand = function(req, res) { logger.info("Responding to client that we did not find a valid resource"); res.set('Content-Type', 'text/plain'); res.send(404, "That URL did not map to a valid JSONWP resource"); }; exports.localScreenshot = function(req, res) { var file = req.body.file; if (checkMissingParams(res, {file: file})) { req.device.localScreenshot(file, getResponseHandler(req, res)); } }; exports.notYetImplemented = notYetImplemented; var mobileCmdMap = { 'tap': exports.mobileTap , 'drag': exports.mobileDrag , 'flick': exports.mobileFlick , 'swipe': exports.mobileSwipe , 'scrollTo': exports.mobileScrollTo , 'shake': exports.mobileShake , 'setLocation' : exports.mobileSetLocation , 'hideKeyboard': exports.hideKeyboard , 'setCommandTimeout': exports.setCommandTimeout , 'getCommandTimeout': exports.getCommandTimeout , 'findAndAct': exports.findAndAct , 'setValue' : exports.setValueImmediate , 'reset' : exports.reset , 'lock' : exports.lock , 'background' : exports.background , 'keyevent' : exports.keyevent , 'leaveWebView': exports.leaveWebView , 'fireEvent': exports.fireEvent , 'source': exports.mobileSource , 'find': exports.find , 'waitForPageLoad': exports.waitForPageLoad , 'currentActivity': exports.getCurrentActivity , 'findElementNameContains': exports.findElementNameContains , 'installApp': exports.installApp , 'removeApp': exports.removeApp , 'isAppInstalled': exports.isAppInstalled , 'launchApp': exports.launchApp , 'closeApp': exports.closeApp , 'rotate': exports.mobileRotation , 'longClick' : exports.touchLongClick , 'pinchClose': exports.mobilePinchClose , 'pinchOpen': exports.mobilePinchOpen , 'localScreenshot': exports.localScreenshot , 'getStrings': exports.getStrings }; exports.produceError = function(req, res) { req.device.proxy("thisisnotvalidjs", getResponseHandler(req, res)); }; exports.crash = function() { throw new Error("We just tried to crash Appium!"); }; exports.guineaPig = function(req, res) { var params = { serverTime: parseInt(new Date().getTime() / 1000, 10) , userAgent: req.headers['user-agent'] , comment: "None" }; if (req.method === "POST") { params.comment = req.body.comments || params.comment; } res.set('Content-Type', 'text/html'); res.cookie('guineacookie1', 'i am a cookie value', {path: '/'}); res.cookie('guineacookie2', 'cookiƩ2', {path: '/'}); res.cookie('guineacookie3', 'cant access this', { domain: '.blargimarg.com', path: '/'}); res.send(exports.getTemplate('guinea-pig')(params)); }; exports.getTemplate = function(templateName) { return swig.compileFile(path.resolve(__dirname, "templates", templateName + ".html")); };