悠悠小仙仙 发表于 2019-6-18 10:24:46

Appium的实现理解

Appium的实现理解Appium的架构




起步下载appium的源码,并安装依赖:git clone https://github.com/appium/appium.git
npm install启动appium:node .这个启动命令实际是执行的:node build\main.js(package.json中指定了main入口):...
"main": "./build/lib/main.js",
"bin": {
    "appium": "./build/lib/main.js"
},
.../build/main.js是由/lib/main.js经babel翻译后的结果,所以,我们来看下/lib/main.js来理解appium的流程。

(备注:由于appium源码执行都是执行的编译后的方法,即build目录下,因此如果你想要调试进行测试,需要在各个模块build目录下更改调试,如果更改源码,需要gulp transpile进行编译)appium server

appium server端实现了HTTP REST API接口,将client端发来的API请求,解析,发送给执行端。apium server,以及其他的driver(android,ios)都实现了basedriver类。basedriver定义了session的创建,命令的执行方式(cmd执行)。appium server(appium driver)大致的流程为:
[*]解析命令行参数
[*]注册路由方法
[*]解析路由
我们看一下appium server的源码实现。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);
//注册接口路由,参见(appium-base-driver\lib\jsonwp\Mjsonwp.js)
let router = getAppiumRouter(args);
//express server类(appium-base-driver\lib\express\server.js)
//将注册的路由,传递给express注册.
let server = await baseServer(router, args.port, args.address);
try {
    //是否为appium grid的node节点
    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;
}
...
//路由
//appium.js,下面会讲解路由解析
function getAppiumRouter (args) {
let appium = new AppiumDriver(args);
return routeConfiguringFunction(appium);
}
URL路由解析上面说道,路由注册。所有支持的请求都METHOD_MAP这个全局变量里面。它是一个path:commd的对象集合。路由执行过程是:
[*]检查命令类型(是否包含session)
[*]设置代理
[*]检查命令类型与参数
[*]执行命令
我们来详细看一下(routeConfiguringFunction)到底做了什么(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) {
    //(#route_config),是所有的路由配置,key为path,value为method的数组
   //对METHOD_MAP的配置进行绑定
    for (let of _.toPairs(METHOD_MAP)) {
      for (let of _.toPairs(methods)) {
      // set up the express route handler
      buildHandler(app, method, path, spec, driver, isSessionCommand(spec.command));
      }
    }
};
}
//路由绑定
//示例:
/*
'/wd/hub/session': {
    POST: {command: 'createSession', payloadParams: {required: ['desiredCapabilities'], optional: ['requiredCapabilities', 'capabilities']}}
},
即:
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 {
      //判断是否是创建session命令(包含createSession,getStatus,getSessions)
      //是否有session
      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();
      }
      //POST参数检查
      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) {
      validators(...args);
      }
      //!!!!执行命令
      //捕获返回值
      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;
      driverRes = driverRes;
      }
      ...
    } catch (err) {
       = 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(path, (req, res) => {
    B.resolve(asyncHandler(req, res)).done();
});
}创建session 与 executeCommandlib\appium.js上面说了appium server已经启动了,第一件事情,当然是创建session,然后把command交给这个session的不同driver去执行了。

appium先根据caps进行session创建(getDriverForCaps),然后保存InnerDriver到当前session,以后每次执行命令(executeDCommand)会判断是否为appiumdriver的命令,不是则转给相应的driver去执行命令(android,ios等)。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 = await d.createSession(caps, reqCaps, curSessions);
this.sessions = d;
this.attachUnexpectedShutdownHandler(d, innerSessionId);
d.startNewCommandTimeout();

return ;
}
async executeCommand (cmd, ...args) {
if (isAppiumDriverCommand(cmd)) {
    return super.executeCommand(cmd, ...args);
}

let sessionId = args;
return this.sessions.executeCommand(cmd, ...args);
}
在basedriver中executeDCommand其实是调用类的cmd定义的方法。

页: [1]
查看完整版本: Appium的实现理解