In this paper, the implementation process of appium(version:1.6.4-beta) source code is introduced. Inevitably, there are inappropriate expenditures. If you have any problems, you can communicate directly.
(There is no corresponding test in this article)
Appium Architecture
start
Download appium source code and install dependencies:
git clone https://github.com/appium/appium.git npm install
Start appium:node.
This startup command is actually executed: node build main. JS (the main entry is specified in package. json):
... "main": "./build/lib/main.js", "bin": { "appium": "./build/lib/main.js" }, ...
/ build/main.js is the result of babel translation of / lib/main.js, so let's look at / lib/main.js to understand the process of appium.
(Note: Since appium source code execution is a compiled method of execution, that is, under the build directory, if you want to debug and test, you need to change the debugging in the build directory of each module, and if you change the source code, you need gulp transpile to compile)
appium server
The application server implements the HTTP REST API interface, parses and sends API requests from client to the execution. apium server and other drivers (android,ios) implement the basedriver class. Basedriver defines session creation and command execution (cmd execution).
The appium server(appium driver) process is roughly as follows:
- Parsing command-line parameters
- Registered Routing Method
- Analytic routing
Let's have a look. Source code of appium server Realization.
import { server as baseServer } from 'appium-base-driver'; import getAppiumRouter from './appium'; ... async function main (args = null) { //Analytic parameter let parser = getParser(); let throwInsteadOfExit = false; if (args) { args = Object.assign({}, getDefaultArgs(), args); if (args.throwInsteadOfExit) { throwInsteadOfExit = true; delete args.throwInsteadOfExit; } } else { args = parser.parseArgs(); } await logsinkInit(args); await preflightChecks(parser, args, throwInsteadOfExit); //Output welcome information await logStartupInfo(parser, args); //Register interface routing, see (appium-base-driver lib jsonwp Mjsonwp. js) let router = getAppiumRouter(args); //Express server class (appium-base-driver lib express server. js) //Pass the route of registration to express registration. let server = await baseServer(router, args.port, args.address); try { //Is it a node node of appium grid if (args.nodeconfig !== null) { await registerNode(args.nodeconfig, args.address, args.port); } } catch (err) { await server.close(); throw err; } process.once('SIGINT', async function () { logger.info(`Received SIGINT - shutting down`); await server.close(); }); process.once('SIGTERM', async function () { logger.info(`Received SIGTERM - shutting down`); await server.close(); }); logServerPort(args.address, args.port); return server; } ... //Route //appium.js. Here's how routing parsing works function getAppiumRouter (args) { let appium = new AppiumDriver(args); return routeConfiguringFunction(appium); }
URL Routing Resolution
As mentioned above, routing registration. All requests for support are METHOD_MAP In this global variable. It is a set of path:commd objects. The routing execution process is:
- Check the command type (whether session is included)
- Setting agent
- Check command types and parameters
- Executive order
Let's take a detailed look at what routeConfiguring Function does (appium-base-driver lib mjsonwp Mjsonwp. js):
function routeConfiguringFunction (driver) { if (!driver.sessionExists) { throw new Error('Drivers used with MJSONWP must implement `sessionExists`'); } if (!(driver.executeCommand || driver.execute)) { throw new Error('Drivers used with MJSONWP must implement `executeCommand` or `execute`'); } // return a function which will add all the routes to the driver return function (app) { //[METHOD_MAP](#route_config) is an array of all routing configurations, with key as path and value as method. //Binding the configuration of METHOD_MAP for (let [path, methods] of _.toPairs(METHOD_MAP)) { for (let [method, spec] of _.toPairs(methods)) { // set up the express route handler buildHandler(app, method, path, spec, driver, isSessionCommand(spec.command)); } } }; } //Routing binding //Example: /* '/wd/hub/session': { POST: {command: 'createSession', payloadParams: {required: ['desiredCapabilities'], optional: ['requiredCapabilities', 'capabilities']}} }, Namely: method: POST path: /wd/hub/session spec: array driver: appium */ function buildHandler (app, method, path, spec, driver, isSessCmd) { let asyncHandler = async (req, res) => { let jsonObj = req.body; let httpResBody = {}; let httpStatus = 200; let newSessionId; try { //Determine whether to create session commands (including create Session, getStatus, getSessions)? //Is there a session? if (isSessCmd && !driver.sessionExists(req.params.sessionId)) { throw new errors.NoSuchDriverError(); } //If the agent is set, the transmission will be made. if (isSessCmd && driverShouldDoJwpProxy(driver, req, spec.command)) { await doJwpProxy(driver, req, res); return; } //Is the command supported? if (!spec.command) { throw new errors.NotImplementedError(); } //POST parameter checking if (spec.payloadParams && spec.payloadParams.wrap) { jsonObj = wrapParams(spec.payloadParams, jsonObj); } if (spec.payloadParams && spec.payloadParams.unwrap) { jsonObj = unwrapParams(spec.payloadParams, jsonObj); } checkParams(spec.payloadParams, jsonObj); //Structural parameters let args = makeArgs(req.params, jsonObj, spec.payloadParams || []); let driverRes; if (validators[spec.command]) { validators[spec.command](...args); } //!!!!! Execute orders //Capture return value if (driver.executeCommand) { driverRes = await driver.executeCommand(spec.command, ...args); } else { driverRes = await driver.execute(spec.command, ...args); } // unpack createSession response if (spec.command === 'createSession') { newSessionId = driverRes[0]; driverRes = driverRes[1]; } ... } catch (err) { [httpStatus, httpResBody] = getResponseForJsonwpError(actualErr); } if (_.isString(httpResBody)) { res.status(httpStatus).send(httpResBody); } else { if (newSessionId) { httpResBody.sessionId = newSessionId; } else { httpResBody.sessionId = req.params.sessionId || null; } res.status(httpStatus).json(httpResBody); } }; // add the method to the app app[method.toLowerCase()](path, (req, res) => { B.resolve(asyncHandler(req, res)).done(); }); }
Create session and executeCommand
As mentioned above, appium server has been started. The first thing, of course, is to create a session, and then give the command to the different driver s of the session to execute.
Appium first creates session based on caps (get Driver ForCaps), then saves InnerDriver to the current session, and then executes the command (DCommand) to determine whether it is an appium driver command or not, and then transfers it to the corresponding driver to execute the command (android,ios, etc.).
async createSession (caps, reqCaps) { caps = _.defaults(_.clone(caps), this.args.defaultCapabilities); let InnerDriver = this.getDriverForCaps(caps); this.printNewSessionAnnouncement(InnerDriver, caps); if (this.args.sessionOverride && !!this.sessions && _.keys(this.sessions).length > 0) { for (let id of _.keys(this.sessions)) { log.info(` Deleting session '${id}'`); try { await this.deleteSession(id); } catch (ign) { } } } let curSessions; try { curSessions = this.curSessionDataForDriver(InnerDriver); } catch (e) { throw new errors.SessionNotCreatedError(e.message); } let d = new InnerDriver(this.args); let [innerSessionId, dCaps] = await d.createSession(caps, reqCaps, curSessions); this.sessions[innerSessionId] = d; this.attachUnexpectedShutdownHandler(d, innerSessionId); d.startNewCommandTimeout(); return [innerSessionId, dCaps]; } async executeCommand (cmd, ...args) { if (isAppiumDriverCommand(cmd)) { return super.executeCommand(cmd, ...args); } let sessionId = args[args.length - 1]; return this.sessions[sessionId].executeCommand(cmd, ...args); }
In basedriver, executeDCommand is actually a method of calling the cmd definition of a class.
android executes commands
Let's take UI Automator 2 ( appium-ui Automator 2-driver build lib) as an example to see its implementation of cmd.
Take getAttribute (appium-uiautomator 2-driver lib commands element. js) as an example to illustrate:
commands.getAttribute = async function (attribute, elementId) { return await this.uiautomator2.jwproxy.command(`/element/${elementId}/attribute/${attribute}`, 'GET', {}); };
appium forwards HTTP requests from hosts to devices via adb forward
await this.adb.forwardPort(this.opts.systemPort, DEVICE_PORT); //Host port number: 8200,8299 //Equipment port number: 6790