Appium源码分析(4)-控制器模块

来源:互联网 发布:淘宝店铺花呗分期 编辑:程序博客网 时间:2024/06/06 05:23

上一篇分析appium的文章还要追述到一个月前了,感慨已经荒废了很久了。今天继续开始吧。

控制器模块

// 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')  , _ = require('underscore')  , _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  , hasValue = require('appium-support').util.hasValue  , helpers = require('../helpers.js')  , logCustomDeprecationWarning = helpers.logCustomDeprecationWarning  , safely = require('./helpers.js').safely  , NotImplementedError = require('./errors').NotImplementedError;exports.getGlobalBeforeFilter = function (appium) {  return function (req, res, next) {    req.appium = appium;    req.device = appium.device;    if (proxy.shouldProxy(req)) {      if (req.appium.commandTimeout) {        // if we're proxying, we never get into the sessionBeforeFilter,        // so let's make sure to reset the timeout on every request still        req.appium.resetTimeout();      }      if (typeof req.device.translatePath !== "undefined") {        req.device.translatePath(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 (req.appium.commandTimeout) {    req.appium.resetTimeout();  }  // if we don't actually have a valid session, respond with an error  if (sessId && (!req.device || req.appium.sessionId !== sessId)) {    safely(req, function () {      res.status(404).send({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;  }  if (req.device && typeof req.device.getStatusExtensions === "function") {    data = _.extend(data, req.device.getStatusExtensions());  }  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, error);      } 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.device.args.app !== "undefined") {    install(req.device.args.app);  } else {    respondError(req, res, "No app defined (either through desired capabilities or as an argument)");  }};exports.removeApp = function (req, res) {  req.body.appId = req.body.appId || req.body.bundleId;  if (checkMissingParams(req, res, {appId: req.body.appId}, true)) {    req.device.removeApp(req.body.appId, function (error, response) {      if (error !== null) {        respondError(req, res, response);      } else {        respondSuccess(req, res, response);      }    });  }};exports.isAppInstalled = function (req, res) {  if (checkMissingParams(req, res, {bundleId: req.body.bundleId}, true)) {    req.device.isAppInstalled(req.body.bundleId, function (error, stdout) {      if (error !== null) {        respondSuccess(req, res, false);      } else {        // We're examining the type of stdout because `isAppInstalled` uses        // node-idevice for real iOS devices.  node-idevice passes a boolean        // value in the second parameter of the callback function.  Other        // functions pass back an array.  Changing the parameter type of the        // other functions is a deeper and more dangerous change than just        // type-checking here.        if ((req.appium.args.udid && req.appium.args.udid.length === 40) ||            (typeof stdout === "boolean" && stdout) ||            (typeof stdout[0] !== "undefined")) {          respondSuccess(req, res, true);        } else {          respondSuccess(req, res, false);        }      }    });  }};exports.startActivity = function (req, res) {  var onErr = function (err) {    respondError(req, res, "Unable to launch the app: " + err);  };  // 8/21/14: currently not supported on all devices  if (!req.device.startApp) {    onErr(new NotImplementedError());  } else {    req.device.startApp(req.body, function (err) {      if (err) return onErr(err);      respondSuccess(req, res, "Successfully launched the app.");    });  }};exports.launchApp = function (req, res) {  var onErr = function (err) {    respondError(req, res, "Unable to launch the app: " + err);  };  req.device.start(function (err) {    if (err) return onErr(err);    respondSuccess(req, res, "Successfully launched the app.");  }, function () {    onErr(new Error("UiAutomator died"));  });};exports.closeApp = function (req, res) {  req.device.stop(function () {    respondSuccess(req, res, "Successfully closed the [" + req.device.args.app + "] app.");  }, function () {    respondError(req, res, "Something went wrong whilst closing the [" + req.device.args.app + "] app.");  });};exports.createSession = function (req, res) {  if (typeof req.body === 'string') {    req.body = JSON.parse(req.body);  }  logger.info('Client User-Agent string:', req.headers['user-agent']);  var next = function (reqHost, sessionId) {    safely(req, function () {      res.set('Location', "http://" + reqHost + "/wd/hub/session/" + sessionId);      res.status(303).send("Appium session started with sessionId " + sessionId);    });  };  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);        logger.debug(err.stack);        delete err.stack;        respondError(req, res, status.codes.SessionNotCreatedException, err);      } else {        logger.debug("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(req, res, {seconds: seconds})) {    req.device.lock(seconds, getResponseHandler(req, res));  }};exports.unlock = function (req, res) {  if (req.device.unlock) {    req.device.unlock(getResponseHandler(req, res));  } else {    notYetImplemented(req, res);  }};exports.isLocked = function (req, res) {  req.device.isLocked(getResponseHandler(req, res));};exports.background = function (req, res) {  var seconds = req.body.seconds;  if (checkMissingParams(req, 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(req, 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(req, 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      // spec says value attribute is an array of strings;      // let's turn it into one string    , value = req.body.value.join('');  req.device.setValue(elementId, value, getResponseHandler(req, res));};exports.replaceValue = function (req, res) {  var elementId = req.params.elementId      // spec says value attribute is an array of strings;      // let's turn it into one string    , value = req.body.value.join('');  req.device.replaceValue(elementId, value, getResponseHandler(req, res));};exports.performTouch = function (req, res) {  // touch actions do not work in webview  if (req.device.isWebContext()) {    return notYetImplemented(req, res);  }  // first, assume that we are getting and array of gestures  var gestures = req.body;  // some clients, like Python, send an object in which there is an `actions`  // property that is the array of actions  // get the gestures from there if we don't already have an array  if (gestures && !Array.isArray(gestures)) {    gestures = gestures.actions;  }  // press-wait-moveTo-release is `swipe`, so use native method  if (gestures.length === 4 &&      gestures[0].action === 'press' &&      gestures[1].action === 'wait' &&      gestures[2].action === 'moveTo' &&      gestures[3].action === 'release') {      return exports.mobileSwipe(req, res, gestures);  }  req.device.performTouch(gestures, getResponseHandler(req, res));};exports.performMultiAction = function (req, res) {  // touch actions do not work in webview  if (req.device.isWebContext()) {    return notYetImplemented(req, res);  }  var elementId = req.body.elementId;  var actions = req.body.actions;  if (actions.length === 0) {    return respondError(req, res, status.codes.UnknownError.code,      new Error("Unable to perform Multi Pointer Gesture with no actions."));  }  req.device.performMultiAction(elementId, actions, 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;  var x = req.body.x;  var y = req.body.y;  var duration = req.body.duration;  if (element && checkMissingParams(req, res, {element: element}, true)) {    req.device.touchLongClick(element, x, y, duration, getResponseHandler(req, res));  } else if (checkMissingParams(req, res, {x: x, y: y}, true)) {    req.device.touchLongClick(element, x, y, duration, getResponseHandler(req, res));  }};exports.touchDown = function (req, res) {  var element = req.body.element;  var x = req.body.x;  var y = req.body.y;  if (element && checkMissingParams(req, res, {element: element}, true)) {    req.device.touchDown(element, x, y, getResponseHandler(req, res));  } else if (checkMissingParams(req, res, {x: x, y: y}, true)) {    req.device.touchDown(element, x, y, getResponseHandler(req, res));  }};exports.touchUp = function (req, res) {  var element = req.body.element;  var x = req.body.x;  var y = req.body.y;  if (element && checkMissingParams(req, res, {element: element}, true)) {    req.device.touchUp(element, x, y, getResponseHandler(req, res));  } else if (checkMissingParams(req, res, {x: x, y: y}, true)) {    req.device.touchUp(element, x, y, getResponseHandler(req, res));  }};exports.touchMove = function (req, res) {  var element = req.body.element;  var x = req.body.x;  var y = req.body.y;  if (element && checkMissingParams(req, res, {element: element}, true)) {    req.device.touchMove(element, x, y, getResponseHandler(req, res));  } else if (checkMissingParams(req, res, {x: x, y: y}, true)) {    req.device.touchMove(element, x, y, 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  , duration: 1  , touchCount: 1  , element: null  , destEl: null  });  var touchCount = req.body.touchCount    , element = req.body.element    , destEl = req.body.destEl    , duration = req.body.duration    , startX = req.body.startX    , startY = req.body.startY    , endX = req.body.endX    , endY = req.body.endY;  req.device.drag(startX, startY, endX, endY, duration, touchCount, element, destEl, getResponseHandler(req, res));};var _getSwipeTouchDuration = function (waitGesture) {  // the touch action api uses ms, we want seconds  // 0.8 is the default time for the operation  var duration = 0.8;  if (typeof waitGesture.options.ms !== 'undefined' && waitGesture.options.ms) {    duration = waitGesture.options.ms / 1000;    if (duration === 0) {      // set to a very low number, since they wanted it fast      // but below 0.1 becomes 0 steps, which causes errors      duration = 0.1;    }  }  return duration;};exports.mobileSwipe = function (req, res, gestures) {  var getCoordDefault = function (val) {    // going the long way and checking for undefined and null since    // we can't be assured `elId` is a string and not an int. Same    // thing with destElement below.    return hasValue(val) ? val : 0.5;  };  var touchCount = req.body.touchCount || 1    , startX =  getCoordDefault(gestures[0].options.x)    , startY = getCoordDefault(gestures[0].options.y)    , endX = getCoordDefault(gestures[2].options.x)    , endY = getCoordDefault(gestures[2].options.y)    , duration = _getSwipeTouchDuration(gestures[1])    , element = gestures[0].options.element    , destElement = gestures[2].options.element || gestures[0].options.element;  // there's no destination element handling in bootstrap and since it applies to all platforms, we handle it here  if (hasValue(destElement)) {    req.device.getLocation(destElement, function (err, locResult) {      if (err) {        respondError(req, res, err);      } else {        req.device.getSize(destElement, function (er, sizeResult) {          if (er) {            respondError(req, res, er);          } else {            var offsetX = (Math.abs(endX) < 1 && Math.abs(endX) > 0) ?                      sizeResult.value.width * endX :                      endX;            var offsetY = (Math.abs(endY) < 1 && Math.abs(endY) > 0) ?                      sizeResult.value.height * endY :                      endY;            var destX = locResult.value.x + offsetX;            var destY = locResult.value.y + offsetY;            // if the target element was provided, the coordinates for the destination need to be relative to it.            if (hasValue(element)) {              req.device.getLocation(element, function (e, firstElLocation) {                if (e) {                  respondError(req, res, e);                } else {                  destX -= firstElLocation.value.x;                  destY -= firstElLocation.value.y;                  req.device.swipe(startX, startY, destX, destY, duration, touchCount, element, getResponseHandler(req, res));                }              });            } else {              req.device.swipe(startX, startY, destX, destY, duration, touchCount, element, getResponseHandler(req, res));            }          }        });      }    });  } else {    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) {  logCustomDeprecationWarning('mobile method', 'scrollTo',      "scrollTo will be removed in a future version of appium");  req.body = _.defaults(req.body, {    element: null  , text: null  , direction: "vertical"  });  var element = req.body.element    , text = req.body.text    , direction = req.body.direction;  req.device.scrollTo(element, text, direction, getResponseHandler(req, res));};exports.mobileScroll = function (req, res) {  req.body = _.defaults(req.body, {    element: null  , direction: "down"  });  var direction = req.body.direction.toString().toLowerCase()    , element = req.body.element;  if (!_.contains(['up', 'left', 'right', 'down'], direction)) {    return respondError(req, res, status.codes.UnknownCommand.code,      new Error("Direction " + direction + " is not valid for scroll"));  }  req.device.scroll(element, direction, getResponseHandler(req, res));};exports.mobileShake = function (req, res) {  req.device.shake(getResponseHandler(req, res));};exports.hideKeyboard = function (req, res) {  var key = req.body.key || req.body.keyName;  var strategy = req.body.strategy ||  (key ? 'pressKey' : 'default');  req.device.hideKeyboard(strategy, key, 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) {  logCustomDeprecationWarning('location', 'getLocation', "location will be removed in a future version of Appium, please use location_in_view");  exports.getLocationInView(req, res);};exports.getLocationInView = 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.maximizeWindow = function (req, res) {  // noop  respondSuccess(req, res);};exports.getPageIndex = function (req, res) {  var elementId = req.params.elementId;  req.device.getPageIndex(elementId, getResponseHandler(req, res));};exports.pressKeyCode = function (req, res) {  req.body = _.defaults(req.body, {    keycode: null  , metastate: null  });  var keycode = req.body.keycode  , metastate = req.body.metastate;  req.device.pressKeyCode(keycode, metastate, getResponseHandler(req, res));};exports.longPressKeyCode = function (req, res) {  req.body = _.defaults(req.body, {    keycode: null  , metastate: null  });  var keycode = req.body.keycode  , metastate = req.body.metastate;  req.device.longPressKeyCode(keycode, metastate, 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.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.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.pageLoadTimeout = function (req, res) {  var ms = req.body.ms;  req.device.pageLoadTimeout(ms, getResponseHandler(req, res));};exports.timeouts = function (req, res) {  var timeoutType = req.body.type    , ms = req.body.ms;  if (checkMissingParams(req, res, {type: timeoutType, ms: ms})) {    if (timeoutType === "implicit") {      exports.implicitWait(req, res);    } else if (timeoutType === "script") {      exports.asyncScriptTimeout(req, res);    } else if (timeoutType === "command") {      var secs = parseInt(ms, 10) / 1000;      req.appium.setCommandTimeout(secs, getResponseHandler(req, res));    } else if (timeoutType === "page load") {      exports.pageLoadTimeout(req, res);    } else {      respondError(req, res, status.codes.UnknownCommand.code,        new Error("Invalid timeout '" + timeoutType + "'"));    }  }};exports.setOrientation = function (req, res) {  var orientation = req.body.orientation;  req.device.setOrientation(orientation, getResponseHandler(req, res));};exports.setLocation = function (req, res) {  if (req.body.latitude || req.body.longitude) {    logCustomDeprecationWarning('geolocation', 'wrongbody',         "Use of the set location method with the latitude and longitude " +         "params as top-level JSON params is deprecated and will be removed. " +         "Please update your client library to use a method that conforms " +         "to the spec");  }  var location = req.body.location || {};  var latitude = location.latitude || req.body.latitude    , longitude = location.longitude || req.body.longitude    , altitude = location.altitude || req.body.altitude || null;  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(req, 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(req, 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(req, 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.callbackAddress + ':' + req.appium.args.callbackPort;  responseUrl += '/wd/hub/session/' + req.appium.sessionId + '/receive_async_response';  if (checkMissingParams(req, 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 = {};  var suppMethods = req.device.mobileMethodsSupported;  if (suppMethods && !_.contains(suppMethods, cmd)) {    return respondError(req, res, status.codes.UnknownCommand.code,      new Error("That device doesn't know how to respond to 'mobile: '" +                cmd + "--it's probably not using Appium's API"));  }  if (args.length) {    if (args.length !== 1) {      safely(req, function () {        res.status(400).send("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.debug("Tried to execute non-existent mobile command '" + cmd + "'" +                ". Most mobile commands have been ported to official client " +                "library methods. Please check your Appium library for more " +                "information and documentation");    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(req, 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.setContext = function (req, res) {  var name = req.body.name;  if (checkMissingParams(req, res, {name: name})) {    req.device.setContext(name, getResponseHandler(req, res));  }};exports.getCurrentContext = function (req, res) {  req.device.getCurrentContext(getResponseHandler(req, res));};exports.getContexts = function (req, res) {  req.device.getContexts(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(req, 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(req, res, {timeout: timeout})) {    timeout = parseInt(timeout, 10);    req.appium.setCommandTimeout(timeout, getResponseHandler(req, res));  }};exports.receiveAsyncResponse = function (req, res) {  var asyncResponse = req.body;  req.device.receiveAsyncResponse(asyncResponse);  safely(req, function () {    res.sendStatus(200);  });};exports.setValueImmediate = function (req, res) {  var element = req.params.elementId    , value = req.body.value;  if (checkMissingParams(req, res, {element: element, value: value})) {    req.device.setValueImmediate(element, value, 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(req, 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));};exports.getLog = function (req, res) {  var logType = req.body.type;  if (checkMissingParams(req, 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.body = _.defaults(req.body, {    language: null,    stringFile: null  });  var language = req.body.language,      stringFile = req.body.stringFile;  req.device.getStrings(language, stringFile, getResponseHandler(req, res));};exports.unknownCommand = function (req, res) {  logger.debug("Responding to client that we did not find a valid resource");  safely(req, function () {    res.set('Content-Type', 'text/plain');    res.status(404).send("That URL did not map to a valid JSONWP resource");  });};exports.pushFile = function (req, res) {  var data = req.body.data; // base64 data  var path = req.body.path; // remote path  if (checkMissingParams(req, res, {data: data, path: path})) {    req.device.pushFile(data, path, getResponseHandler(req, res));  }};exports.pullFile = function (req, res) {  var path = req.body.path; // remote path  if (checkMissingParams(req, res, {path: path})) {    req.device.pullFile(path, getResponseHandler(req, res));  }};exports.pullFolder = function (req, res) {  var path = req.body.path; // remote path  if (checkMissingParams(req, res, {path: path})) {    req.device.pullFolder(path, getResponseHandler(req, res));  }};exports.endCoverage = function (req, res) {  var intent = req.body.intent;  var path = req.body.path;  if (checkMissingParams(req, res, {intent: intent, path: path})) {    req.device.endCoverage(intent, path, getResponseHandler(req, res));  }};exports.toggleData = function (req, res) {  req.device.toggleData(getResponseHandler(req, res));};exports.toggleFlightMode = function (req, res) {  req.device.toggleFlightMode(getResponseHandler(req, res));};exports.toggleWiFi = function (req, res) {  req.device.toggleWiFi(getResponseHandler(req, res));};exports.toggleLocationServices = function (req, res) {  req.device.toggleLocationServices(getResponseHandler(req, res));};exports.notYetImplemented = notYetImplemented;var mobileCmdMap = {  'tap': exports.mobileTap, 'drag': exports.mobileDrag, 'flick': exports.mobileFlick, 'scrollTo': exports.mobileScrollTo, 'scroll': exports.mobileScroll, 'longClick' : exports.touchLongClick, 'down' : exports.touchDown, 'up' : exports.touchUp, 'move' : exports.touchMove, 'pinchClose': exports.mobilePinchClose, 'pinchOpen': exports.mobilePinchOpen};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 delay = req.param('delay') ? parseInt(req.param('delay'), 10) : 0;  setTimeout(function () {   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;    }    safely(req, function () {      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));    });  }, delay);};exports.welcome = function (req, res) {  var params = { message: 'Let\'s browse!' };  res.send(exports.getTemplate('welcome')(params));};exports.getTemplate = function (templateName) {  return swig.compileFile(path.resolve(__dirname, "templates",        templateName + ".html"));};exports.openNotifications = function (req, res) {  req.device.openNotifications(getResponseHandler(req, res));};exports.availableIMEEngines = function (req, res) {  req.device.availableIMEEngines(getResponseHandler(req, res));};exports.isIMEActivated = function (req, res) {  req.device.isIMEActivated(getResponseHandler(req, res));};exports.getActiveIMEEngine = function (req, res) {  req.device.getActiveIMEEngine(getResponseHandler(req, res));};exports.activateIMEEngine = function (req, res) {  var imeId = req.body.engine;  req.device.activateIMEEngine(imeId, getResponseHandler(req, res));};exports.deactivateIMEEngine = function (req, res) {  req.device.deactivateIMEEngine(getResponseHandler(req, res));};exports.getNetworkConnection = function (req, res) {  req.device.getNetworkConnection(getResponseHandler(req, res));};exports.setNetworkConnection = function (req, res) {  var type = req.body.type || req.body.parameters.type;  req.device.setNetworkConnection(type, getResponseHandler(req, res));};exports.getSettings = function (req, res) {  req.device.getSettings(getResponseHandler(req, res));};exports.updateSettings = function (req, res) {  var settings = req.body.settings || req.body.parameters.settings;  if (checkMissingParams(req, res, {settings: settings})) {    req.device.updateSettings(settings, getResponseHandler(req, res));  }};

这里面定义了一系列的方法,这些方法是一个一个单独的存在,作用就是在路由器模块中定义的一些映射,一一对应的关系,当一个url过来的时候,会调用对应的方法,这些方法就定义在控制器模块中。
上面的方法大多数都会调用appium.js中device模块,以及都会使用getResponseHandler方法,所以我们从一个doClick方法开始讲解这两个点的作用。

doClick

exports.doClick = function (req, res) {  var elementId = req.params.elementId || req.body.element;  req.device.click(elementId, getResponseHandler(req, res));};

首先我们来看一下什么样的请求发过来的时候,会调用该方法,去routing.js模块中查找:

rest.post('/wd/hub/session/:sessionId?/element/:elementId?/click', controller.doClick);

上面的post方法第一个参数是一个字符串,代表url。冒号表示后面跟的是一个叫sessionId的参数,会在访问的url中取得。这个参数匹配的是含有两个参数(sessionId和elementId)的url,如果种url请求发送过来后,会调用doClick方法,然后就到了我们controller中的doClick方法中。该方法会解析出该请求的elementId参数复制给本地变量elementId,然后调用请求对应的appium模块中的device模块的click方法,所以我们一会还要去看device下看看click方法,但是再深入之前,我们先得解决getResponseHandler方法的作用。

getResponseHandler

该方法实际是调用了response.js模块中的如下方法,该方法主要是对我们的返回的内容做一些检查,我们暂且不深入,留到以后。

exports.getResponseHandler = function (req, res) {  return function (err, response) {    if (typeof response === "undefined" || response === null) {      response = {};    }    if (err !== null && typeof err !== "undefined" && typeof err.status !== 'undefined' && typeof err.value !== 'undefined') {      throw new Error("Looks like you passed in a response object as the " +                      "first param to getResponseHandler. Err is always the " +                      "first param! Fix your codes!");    } else if (err !== null && typeof err !== "undefined") {      if (typeof err.name !== 'undefined') {        if (err.name === 'NotImplementedError') {          notImplementedInThisContext(req, res);        } else if (err.name === "NotYetImplementedError") {          notYetImplemented(req, res);        } else {          respondError(req, res, status.codes.UnknownError.code, err);        }      } else {        var value = response.value;        if (typeof value === "undefined") {          value = '';        }        respondError(req, res, err.message, value);      }    } else {      if (response.status === 0) {        respondSuccess(req, res, response.value, response.sessionId);      } else {        respondError(req, res, response.status, response.value);      }    }  };};

device.click

首先我们要了解device的分类,在appium中将device分为如下几类:

var DT_IOS = "ios"  , DT_SAFARI = "safari"  , DT_ANDROID = "android"  , DT_CHROME = "chrome"  , DT_SELENDROID = "selendroid"  , DT_FIREFOX_OS = "firefoxos";

这些分类对应的处理模块为:

IOS = require('./devices/ios/ios.js')  , Safari = require('./devices/ios/safari.js')  , Android = require('./devices/android/android.js')  , Selendroid = require('./devices/android/selendroid.js')  , Chrome = require('./devices/android/chrome.js')  , FirefoxOs = require('./devices/firefoxos/firefoxos.js')

所以根据设备类型的不同,device所指的对象也不一样,但是我们从上面可以得知一点就是:所有和设备相关的信息都保存在devices目录下,我们后期研究devices模块的是就直接切入到这个目录下。现在我们就以android设备来看看click的处理方式。我们在devices/android目录下找到android-controller.js模块

androidController.click = function (elementId, cb) {  this.proxy(["element:click", {elementId: elementId}], cb);};

该proxy方法位于devices目录下的common.js模块中:

exports.proxy = function (command, cb) {  logger.debug('Pushing command to appium work queue: ' + JSON.stringify(command));  this.push([command, cb]);};

上面的方法将传进来的命令直接使用了push方法。
(之所以能加this,是因为在devices/android/android.js将该模块设置为了全局变量,Android.prototype.proxy = deviceCommon.proxy;
那么我们的注意点又调整到了common.js模块中的push方法中了。

该push方法又在android.js中被设置为全局变量:

Android.prototype.push = function (elem) {
this.queue.push({action: elem[0][0], params: elem[0][1] || {}}, elem[1]);
};

该push方法将命令分解然后放到一个队列中,所以我们来参看这个队列。

Android.prototype.initQueue = function () {  this.queue = async.queue(function (task, cb) {    var action = task.action,        params = task.params;    this.cbForCurrentCmd = cb;    if (this.adb && !this.shuttingDown) {      this.uiautomator.sendAction(action, params, function (response) {        this.cbForCurrentCmd = null;        if (typeof cb === 'function') {          this.respond(response, cb);        }      }.bind(this));    } else {      this.cbForCurrentCmd = null;      var msg = "Tried to send command to non-existent Android device, " +                 "maybe it shut down?";      if (this.shuttingDown) {        msg = "We're in the middle of shutting down the Android device, " +              "so your request won't be executed. Sorry!";      }      this.respond({        status: status.codes.UnknownError.code      , value: msg      }, cb);    }  }.bind(this), 1);};

从上面的代码中可以看出来,该队列中的元素需要2个参数:action和params,所以我们向这个队列中添加元素的时候形式如下:
{action: elem[0][0], params: elem[0][1] || {}}, elem[1]

action取得的是命令行中的第一个数组的第一个元素([“element:click”, {elementId: elementId}]中click),params取的是elementId.

遗留问题

response.getResponseHandler

devices目录

ok,先到这,吃饭先!

0 0
原创粉丝点击