Appium日志分析

概述

Appium是一个Nodejs语言编写的基于express框架的web server。

下载源码

从git上获取源码
先进入本地指定的文件夹
git clone https://github.com/appium/appium.git
cd appium
npm install
gulp transpile
注意,这里npm install下载依赖包,可能需要的时间比较长,1-2个小时或者更长。
编译完成后执行node .启动Appium

Appium日志分析

启动appium,端口为4723,这个端口用来接收Appium客户端发送来的HTTP请求

[Appium] Welcome to Appium v1.6.4
[Appium] Appium REST http interface listener started on 0.0.0.0:4723
自动化测试脚本开始运行,创建luadriver,例如执行common里面的setupdriver()函数

启动过程分析

项目结构


gulpfile.js:自动化构建的任务都在里面。如果修改了源码并想要生效,需要执行gulp transpile命令生成build文件夹。
package.json:项目的配制文件,项目的入口在里配置。
找到Package.json的main字段(如下图所示),入口是当前目录下的build目录,但build目录是通过appium gulp插件把es6转化为es5的代码,所以要看源码,不需要看build目录,只需要看lib目录下的main.js,这才是项目的入口.

main.js文件

1.解析命令行参数. 如果启动的时候没有传对应的参数,则使用默认值.

2.初始化日志记录器, appium版本检查,打印出appium版本信息.
3.创建路由函数
getAppiumRouter函数如下:创建了一个AppiumDrvier对象,返回routeConfiguringFunction()的返回值
routeConfiguringFunction()的返回值是一个函数,保留着appiumDriver的引用.
buildHandler就是路由配置函数,当然这里还没有进行配置,只是把函数返回给了上层调用者.
4.创建web server
Appium 用的express框架创建web server,并调用第3步返回的route配置路由.
路由的配置由一个单独的文件,结构如下:

每当请求过来时,都是调用appiumDriver的executeCommand或execute方法,得到返回值,返回给客户端
至此,所有的路由都已经绑定。

创建driver

在appium log中,看到向/wd/hub/session这个路径发送请求,接收到客户端的POST请求,及capabilities参数
[HTTP] --> POST /wd/hub/session {"capabilities":{"deviceName":"D8YDU16325002121","firstMatch":[],"unicodeKeyboard":true,"newCommandTimeout":"120","noReset":false,"alwaysMatch":{},"resetKeyboard":true,"appPackage":"com.boyaa.lordland.sina","platformName":"Android","appActivity":"com.boyaa.lordland.sina.Game"},"desiredCapabilities":{"unicodeKeyboard":true,"deviceName":"D8YDU16325002121","newCommandTimeout":"120","noReset":false,"resetKeyboard":true,"appPackage":"com.boyaa.lordland.sina","platformName":"Android","appActivity":"com.boyaa.lordland.sina.Game"}}
对应的command是:createSession,appium-base-driver\lib\mjsonwp\route.js中查看route
会调用appium driver的executeCommand(‘createSession’)

首次调用会调用父类的executeCommand(‘createSession’),再在父类BaseDriver里找executeCommand方法:

在BaseDriver的executeCommand方法里,直接调用this(‘createSession’)方法

这里需要注意的是,方法的查找是从AppiumDriver往继承链往上查找的,this的作用域应该以AppiumDriver优先,所以this(‘createSession’)的调用应当优先在AppiumDriver里查找。经过查看,AppiumDriver里有createSession方法,并且输出跟appium控制台输出的一致。
createSession方法:

  1. 根据请求类型得到对应的driver,这里是android,得到的是AndroidDriver类.,并且实例化,随后调用createSession创建sessionId.

从上面代码可以看到,创建sessionId后,在AppiumDriver内部作一个”sessionId” -> AndroidDriver的映射.
2.返回sessionId和参数

从appium控制台Log看,在AndroidDriver.createSession里还有一些操作,点进去看看
AndroidDriver.createSessio余下的内容:
1.获取java版本

2.创建adb实例

3.调用getDeviceInfoFromCaps获取连接设备

4.调用startAndroidSession

再看startAndroidSession里干了些什么事

startAndroidSession函数:
1.调用initDevice函数,这个函数会执行如下操作:
1) 执行adb –P –s shell wait-for-device.
2) 开启logcat
3) push settings_apk_debug.apk
4) push unlock_apk-debug.apk

2.获取系统api版本

3.解锁屏幕

4.调用initAUT,这个函数会执行如下操作:

  1. 执行adb –P –s shell am force-stop “pkg”
  2. 执行adb –P –s shell pm clear “pkg”

5.创建AndroidBootstrap对象, 创建对象只是简单的赋值

6.启动AndroidBootstrap,即调用start函数,start函数会做以下操作:
1) 创建UiAutomator对象:构造函数也只是简单的赋值.
2) adb端口转发:4724 -> 4724.
3) 启动UiAutomator,即调用UiAutomator.start函数:
3.1) push Bootstrap.jar -> /data/local/tmp.
3.2) kill uiautomator进程(手机端).
3.3) adb –P –s shell uiautomator runtest xxxx跑Bootstrap.jar,维持一个子进程的引用
4) 连接本地端口4724,维持this.socketClient的引用

6.执行startAUT函数:

  1. 执行adb –P –s am start –W –n “pkg/class” –S –a …..启动app

回到appium driver,至此,会话创建完成

上图对应的appium控制台log如下:
[Appium] New AndroidDriver session created successfully, session a942b672-b86d-44a4-bd46-14a6cd516f11 added to master session list
[debug] [BaseDriver] Event 'newSessionStarted' logged at 1515135795839 (15:03:15 GMT+0800 (中国标准时间))
之后就是把sessionId返回给客户端保存.
以上就是在客户端:
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
这条语句的处理流程。

查找元素

driver.find_element_by_id('xxx').click()
从appium控制台的log看,发送了这样一条请求:

从上图看到,client向/wd/hub/session/:sessionId/element 发送了post请求,调用的是AppiumDriver.findElement方法
但最开始是通过的executeCommand方法,由缓存的AndroidDriver来执行,如下图代码所示:

通过查找,AndroidDirver的executeCommand方法在父类中,进行调用AndroidDriver里findElement方法,调用this.doFindElementOrEls方法,而doFindElementOrEls方法又委托AndroidBootstrap的sendAction方法

在AndroidBootstrap.sendAction方法中,向socket写入json数据(查找策略和查找的值),并把返回的数据解析为json格式,最终返回的是:
{"ELEMENT":"1"}

接着,又向/:sessionId/element/1/click发送请求,1就是上步返回的.

还是老样子,最终调用AndroidBootstrap.sendAction方法

在手机端的点击成功后,返回{“value”:true, “status”:0},最终返回 到客户端就是true

至此,这两条语句的流程已经基本上走完了
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
driver.find_element_by_id('com.android.calculator2:id/digit1').click()

posted on 2018-01-05 15:26  yanzilove  阅读(829)  评论(0)    收藏  举报