上一篇分析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 stillreq.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 errorif (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 clientvar 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 devicesif (!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 clientrespondSuccess(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 webviewif (req.device.isWebContext()) {return notYetImplemented(req, res);}// first, assume that we are getting and array of gesturesvar 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 arrayif (gestures && !Array.isArray(gestures)) {gestures = gestures.actions;}// press-wait-moveTo-release is `swipe`, so use native methodif (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 webviewif (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 operationvar 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 errorsduration = 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 hereif (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) {// nooprespondSuccess(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 datavar path = req.body.path; // remote pathif (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 pathif (checkMissingParams(req, res, {path: path})) {req.device.pullFile(path, getResponseHandler(req, res));}
};exports.pullFolder = function (req, res) {var path = req.body.path; // remote pathif (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,先到这,吃饭先!

Appium源码分析(4)-控制器模块相关推荐

  1. 手机自动化测试:Appium源码分析之跟踪代码分析四 1

    手机自动化测试:Appium源码分析之跟踪代码分析四 控制器模块 // Appium webserver controller methods // https://github.com/hugs/a ...

  2. elasticsearch源码分析之search模块(server端)

    elasticsearch源码分析之search模块(server端) 继续接着上一篇的来说啊,当client端将search的请求发送到某一个node之后,剩下的事情就是server端来处理了,具体 ...

  3. elasticsearch源码分析之search模块(client端)

    elasticsearch源码分析之search模块(client端) 注意,我这里所说的都是通过rest api来做的搜索,所以对于接收到请求的节点,我姑且将之称之为client端,其主要的功能我们 ...

  4. 手机自动化测试:appium源码分析之bootstrap八

    手机自动化测试:appium源码分析之bootstrap八 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大 ...

  5. 手机自动化测试:appium源码分析之bootstrap十二

    手机自动化测试:appium源码分析之bootstrap十二 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请 ...

  6. 手机自动化测试:appium源码分析之bootstrap七

    手机自动化测试:appium源码分析之bootstrap七 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.poptest测试开发 ...

  7. FreeCAD源码分析:FreeCADGui模块

    FreeCAD源码分析:FreeCADGui模块 济南友泉软件有限公司 FreeCADGui项目实现了界面操作.模型显示与交互等相关功能,项目构建生成FreeCAD(_d).dll动态链接库. Fre ...

  8. Python3.5源码分析-内建模块builtins初始化

    Python3源码分析 本文环境python3.5.2. 参考书籍<<Python源码剖析>> python官网 Python3模块初始化与加载 Python的模块分为内建的模 ...

  9. dubbo源码分析系列——dubbo-cluster模块源码分析

    2019独角兽企业重金招聘Python工程师标准>>> 模块功能介绍 该模块的使用介绍请参考dubbo官方用户手册如下章节内容. 集群容错 负载均衡 路由规则 配置规则 注册中心参考 ...

最新文章

  1. 即使连网了ping也会失败
  2. vue 分模块打包 脚手架_vue-cli分模块独立打包
  3. 25 类:接口 抽象父类 多态 鸭子类型 格式化方法与析构方法 反射 异常处理 自定义异常 断言...
  4. java pl0 四元式,【编译原理】c++实现自下而上语法分析及中间代码(四元式)生成...
  5. QT实现SameGame
  6. 保护导师,从我做起;爱护博导,人人有责
  7. Python-selenium-操作元素
  8. 页面之间传递参数得几种方法
  9. 安全公司本意告警用户,不料先遭攻击并泄露超50亿个人数据
  10. 中台之上(一):重视业务架构,不要让“业务的归业务、技术的归技术”
  11. NLP一键中文数据增强工具
  12. cloud-api-commons抽取公共类
  13. python学习-- Django根据现有数据库,自动生成models模型文件
  14. Servlet+JSP分页
  15. Adapter与AdapterView
  16. 【读书总结】《三体》—— 生存是文明的第一需要
  17. HTML5 CSS3 专题 诱人的实例 3D旋转木马效果相册
  18. 【12c】扩展数据类型(Extended Data Types)-- MAX_STRING_SIZE
  19. 哈工大软件过程与工具复习总结
  20. 坑爹的MSN登录错误80072745

热门文章

  1. 2021-2027全球与中国火灾报警装置市场现状及未来发展趋势
  2. 等保评测要求和评测材料需要哪些 你想知道的全攻略都在这里
  3. C语言编程齿轮轮廓线坐标,【100分高分】如何用C语言画齿轮?
  4. 记录一次mvn命令 找不到jar包问题
  5. 高铁JRU设备的作用
  6. 品牌电商如何逆势增长?在这里,一起预见未来~
  7. 邮件服务器过滤,Winmail 邮件服务器软件
  8. php面向对象编写计算器,使用面向对象的图形计算器,面向对象图形计算器_PHP教程...
  9. 将手绘地图或自制地图显示在网页上(利用百度API)
  10. 整数(秒数)转换为时分秒格式(xxxxxx)