本文转自:TesterHome的文章
本文针对appium(version:1.6.4-beta)「比较粗糙」的介绍了下它的源码的实现流程。难免有不妥支出,有任何问题,可直接沟通交流。
(本文中没有相应的测试)
Appium的架构
下载appium的源码,并安装依赖:
1 2
| git clone https: npm install
|
启动appium:
这个启动命令实际是执行的:
(package.json中指定了main入口):
1 2 3 4 5 6 7
| ... "main": "./build/lib/main.js", <!-- more --> "bin": { "appium": "./build/lib/main.js" }, ...
|
/build/main.js是由/lib/main.js经babel翻译后的结果,所以,我们来看下/lib/main.js来理解appium的流程。
(备注:由于appium源码执行都是执行的编译后的方法,即build目录下,因此如果你想要调试进行测试,需要在各个模块build目录下更改调试,如果更改源码,需要gulp transpile进行编译)
appium server端实现了HTTP REST API接口,将client端发来的API请求,解析,发送给执行端。apium server,以及其他的driver(android,ios)都实现了basedriver类。basedriver定义了session的创建,命令的执行方式(cmd执行)。
appium server(appium driver)大致的流程为:
我们看一下appium server的源码实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| import { server as baseServer } from 'appium-base-driver'; import getAppiumRouter from './appium'; ...
async function main (args = null) { 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); await logStartupInfo(parser, args); let router = getAppiumRouter(args); let server = await baseServer(router, args.port, args.address); try { 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; } ...
function getAppiumRouter (args) { let appium = new AppiumDriver(args); return routeConfiguringFunction(appium); }
|
URL路由解析
上面说道,路由注册。所有支持的请求都METHOD_MAP这个全局变量里面。它是一个path:commd的对象集合。路由执行过程是:
我们来详细看一下(routeConfiguringFunction)到底做了什么(appium-base-driver\lib\mjsonwp\Mjsonwp.js):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| 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 function (app) { for (let [path, methods] of _.toPairs(METHOD_MAP)) { for (let [method, spec] of _.toPairs(methods)) { buildHandler(app, method, path, spec, driver, isSessionCommand(spec.command)); } } }; }
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 { if (isSessCmd && !driver.sessionExists(req.params.sessionId)) { throw new errors.NoSuchDriverError(); } if (isSessCmd && driverShouldDoJwpProxy(driver, req, spec.command)) { await doJwpProxy(driver, req, res); return; } if (!spec.command) { throw new errors.NotImplementedError(); } 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); let args = makeArgs(req.params, jsonObj, spec.payloadParams || []); let driverRes; if (validators[spec.command]) { validators[spec.command](...args); } if (driver.executeCommand) { driverRes = await driver.executeCommand(spec.command, ...args); } else { driverRes = await driver.execute(spec.command, ...args); }
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); } }; app[method.toLowerCase()](path, (req, res) => { B.resolve(asyncHandler(req, res)).done(); }); }
|
lib\appium.js
上面说了
已经启动了,第一件事情,当然是创建session,然后把command交给这个session的不同driver去执行了。
appium先根据caps进行session创建(
),然后保存InnerDriver到当前session,以后每次执行命令(executeDCommand)会判断是否为appiumdriver的命令,不是则转给相应的driver去执行命令(android,ios等)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 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); }
|
在basedriver中
其实是调用类的
定义的方法。
我们以
(\appium-uiautomator2-driver\build\lib)为例看一下它的
执行情况。
以
(appium-uiautomator2-driver\lib\commands\element.js)为例说明:
1 2 3
| commands.getAttribute = async function (attribute, elementId) { return await this.uiautomator2.jwproxy.command(`/element/${elementId}/attribute/${attribute}`, 'GET', {}); };
|
appium 通过adb forward将主机的HTTP请求转发到设备中
1 2 3
| await this.adb.forwardPort(this.opts.systemPort, DEVICE_PORT);
|