51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 2470|回复: 0
打印 上一主题 下一主题

Appium的实现理解

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2019-6-18 10:24:46 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
Appium的实现理解Appium的架构




起步

下载appium的源码,并安装依赖:

  1. git clone https://github.com/appium/appium.git
  2. npm install
复制代码

启动appium:node .

这个启动命令实际是执行的:node build\main.js(package.json中指定了main入口):

  1. ...
  2. "main": "./build/lib/main.js",
  3.   "bin": {
  4.     "appium": "./build/lib/main.js"
  5.   },
  6. ...
复制代码

/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的源码实现。

  1. import { server as baseServer } from 'appium-base-driver';
  2. import getAppiumRouter from './appium';
  3. ...

  4. async function main (args = null) {
  5.   //解析参数
  6.   let parser = getParser();
  7.   let throwInsteadOfExit = false;
  8.   if (args) {
  9.     args = Object.assign({}, getDefaultArgs(), args);
  10.     if (args.throwInsteadOfExit) {
  11.       throwInsteadOfExit = true;
  12.       delete args.throwInsteadOfExit;
  13.     }
  14.   } else {
  15.     args = parser.parseArgs();
  16.   }
  17.   await logsinkInit(args);
  18.   await preflightChecks(parser, args, throwInsteadOfExit);
  19.   //输出欢迎信息
  20.   await logStartupInfo(parser, args);
  21.   //注册接口路由,参见(appium-base-driver\lib\jsonwp\Mjsonwp.js)
  22.   let router = getAppiumRouter(args);
  23.   //express server类(appium-base-driver\lib\express\server.js)
  24.   //将注册的路由,传递给express注册.
  25.   let server = await baseServer(router, args.port, args.address);
  26.   try {
  27.     //是否为appium grid的node节点
  28.     if (args.nodeconfig !== null) {
  29.       await registerNode(args.nodeconfig, args.address, args.port);
  30.     }
  31.   } catch (err) {
  32.     await server.close();
  33.     throw err;
  34.   }

  35.   process.once('SIGINT', async function () {
  36.     logger.info(`Received SIGINT - shutting down`);
  37.     await server.close();
  38.   });

  39.   process.once('SIGTERM', async function () {
  40.     logger.info(`Received SIGTERM - shutting down`);
  41.     await server.close();
  42.   });

  43.   logServerPort(args.address, args.port);

  44.   return server;
  45. }
  46. ...
  47. //路由
  48. //appium.js,下面会讲解路由解析
  49. function getAppiumRouter (args) {
  50.   let appium = new AppiumDriver(args);
  51.   return routeConfiguringFunction(appium);
  52. }
复制代码
URL路由解析

上面说道,路由注册。所有支持的请求都METHOD_MAP这个全局变量里面。它是一个path:commd的对象集合。路由执行过程是:

  • 检查命令类型(是否包含session)
  • 设置代理
  • 检查命令类型与参数
  • 执行命令

我们来详细看一下(routeConfiguringFunction)到底做了什么(appium-base-driver\lib\mjsonwp\Mjsonwp.js):


  1. function routeConfiguringFunction (driver) {
  2.   if (!driver.sessionExists) {
  3.     throw new Error('Drivers used with MJSONWP must implement `sessionExists`');
  4.   }
  5.   if (!(driver.executeCommand || driver.execute)) {
  6.     throw new Error('Drivers used with MJSONWP must implement `executeCommand` or `execute`');
  7.   }
  8.   // return a function which will add all the routes to the driver
  9.   return function (app) {
  10.     //[METHOD_MAP](#route_config),是所有的路由配置,key为path,value为method的数组
  11.    //对METHOD_MAP的配置进行绑定
  12.     for (let [path, methods] of _.toPairs(METHOD_MAP)) {
  13.       for (let [method, spec] of _.toPairs(methods)) {
  14.         // set up the express route handler
  15.         buildHandler(app, method, path, spec, driver, isSessionCommand(spec.command));
  16.       }
  17.     }
  18.   };
  19. }
  20. //路由绑定
  21. //示例:
  22. /*
  23.   '/wd/hub/session': {
  24.     POST: {command: 'createSession', payloadParams: {required: ['desiredCapabilities'], optional: ['requiredCapabilities', 'capabilities']}}
  25.   },
  26. 即:
  27. method: POST
  28. path: /wd/hub/session
  29. spec: array
  30. driver: appium
  31. */
  32. function buildHandler (app, method, path, spec, driver, isSessCmd) {
  33.   let asyncHandler = async (req, res) => {
  34.     let jsonObj = req.body;
  35.     let httpResBody = {};
  36.     let httpStatus = 200;
  37.     let newSessionId;
  38.     try {
  39.       //判断是否是创建session命令(包含createSession,getStatus,getSessions)
  40.       //是否有session
  41.       if (isSessCmd && !driver.sessionExists(req.params.sessionId)) {
  42.         throw new errors.NoSuchDriverError();
  43.       }
  44.       //设置了代理则透传
  45.       if (isSessCmd && driverShouldDoJwpProxy(driver, req, spec.command)) {
  46.         await doJwpProxy(driver, req, res);
  47.         return;
  48.       }
  49.       //命令是否支持
  50.       if (!spec.command) {
  51.         throw new errors.NotImplementedError();
  52.       }
  53.       //POST参数检查
  54.       if (spec.payloadParams && spec.payloadParams.wrap) {
  55.         jsonObj = wrapParams(spec.payloadParams, jsonObj);
  56.       }
  57.       if (spec.payloadParams && spec.payloadParams.unwrap) {
  58.         jsonObj = unwrapParams(spec.payloadParams, jsonObj);
  59.       }
  60.       checkParams(spec.payloadParams, jsonObj);
  61.       //构造参数
  62.       let args = makeArgs(req.params, jsonObj, spec.payloadParams || []);
  63.       let driverRes;
  64.       if (validators[spec.command]) {
  65.         validators[spec.command](...args);
  66.       }
  67.       //!!!!执行命令
  68.       //捕获返回值
  69.       if (driver.executeCommand) {
  70.         driverRes = await driver.executeCommand(spec.command, ...args);
  71.       } else {
  72.         driverRes = await driver.execute(spec.command, ...args);
  73.       }

  74.       // unpack createSession response
  75.       if (spec.command === 'createSession') {
  76.         newSessionId = driverRes[0];
  77.         driverRes = driverRes[1];
  78.       }
  79.       ...
  80.     } catch (err) {
  81.       [httpStatus, httpResBody] = getResponseForJsonwpError(actualErr);
  82.     }
  83.     if (_.isString(httpResBody)) {
  84.       res.status(httpStatus).send(httpResBody);
  85.     } else {
  86.       if (newSessionId) {
  87.         httpResBody.sessionId = newSessionId;
  88.       } else {
  89.         httpResBody.sessionId = req.params.sessionId || null;
  90.       }

  91.       res.status(httpStatus).json(httpResBody);
  92.     }
  93.   };
  94.   // add the method to the app
  95.   app[method.toLowerCase()](path, (req, res) => {
  96.     B.resolve(asyncHandler(req, res)).done();
  97.   });
  98. }
复制代码
创建session 与 executeCommand

lib\appium.js

上面说了appium server已经启动了,第一件事情,当然是创建session,然后把command交给这个session的不同driver去执行了。

appium先根据caps进行session创建(getDriverForCaps),然后保存InnerDriver到当前session,以后每次执行命令(executeDCommand)会判断是否为appiumdriver的命令,不是则转给相应的driver去执行命令(android,ios等)。

  1. async createSession (caps, reqCaps) {
  2.   caps = _.defaults(_.clone(caps), this.args.defaultCapabilities);
  3.   let InnerDriver = this.getDriverForCaps(caps);
  4.   this.printNewSessionAnnouncement(InnerDriver, caps);

  5.   if (this.args.sessionOverride && !!this.sessions && _.keys(this.sessions).length > 0) {
  6.     for (let id of _.keys(this.sessions)) {
  7.       log.info(`    Deleting session '${id}'`);
  8.       try {
  9.         await this.deleteSession(id);
  10.       } catch (ign) {
  11.       }
  12.     }
  13.   }

  14.   let curSessions;
  15.   try {
  16.     curSessions = this.curSessionDataForDriver(InnerDriver);
  17.   } catch (e) {
  18.     throw new errors.SessionNotCreatedError(e.message);
  19.   }

  20.   let d = new InnerDriver(this.args);
  21.   let [innerSessionId, dCaps] = await d.createSession(caps, reqCaps, curSessions);
  22.   this.sessions[innerSessionId] = d;
  23.   this.attachUnexpectedShutdownHandler(d, innerSessionId);
  24.   d.startNewCommandTimeout();

  25.   return [innerSessionId, dCaps];
  26. }
  27.   async executeCommand (cmd, ...args) {
  28.   if (isAppiumDriverCommand(cmd)) {
  29.     return super.executeCommand(cmd, ...args);
  30.   }

  31.   let sessionId = args[args.length - 1];
  32.   return this.sessions[sessionId].executeCommand(cmd, ...args);
  33. }
复制代码

在basedriver中executeDCommand其实是调用类的cmd定义的方法。



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

x
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

本版积分规则

关闭

站长推荐上一条 /1 下一条

小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

GMT+8, 2024-11-9 09:41 , Processed in 0.065640 second(s), 26 queries .

Powered by Discuz! X3.2

© 2001-2024 Comsenz Inc.

快速回复 返回顶部 返回列表