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) {
- //[METHOD_MAP](#route_config),是所有的路由配置,key为path,value为method的数组
- //对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));
- }
- }
- };
- }
- //路由绑定
- //示例:
- /*
- '/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[spec.command]) {
- validators[spec.command](...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[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();
- });
- }
复制代码 创建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 [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中executeDCommand其实是调用类的cmd定义的方法。
|