Appium 是由 Node.js 来实现的 HTTP 服务,它并不是一套全新的框架,而是将现有的优秀的框架进行了集成,在 Selenium WebDriver 协议(JsonWireProtocol/Restful web service)的基础上增加了移动端的支持,使 Appium 满足多方面的需求。
官方提供更详细的 Appium 结构说明:https://appium.io/docs/en/contributing-to-appium/appium-packages/
使用 Appium 进行测试时,会产生大量日志,一旦运行过程中遇到报错,可以通过 Appium 服务端的日志以及客户端的日志分析排查问题。
通过命令行的方式启动 Appium Server,下面来分析一下启动日志,日志第一行显示了 Appium 版本信息和服务在本地的运行地址。
$ appium -g appium.log
[Appium] Welcome to Appium v1.8.0-beta3 (REV \
40e40975ebd3593d08c3f83de2546258f7ddf11d)
[Appium] Appium REST http interface listener started \
on 0.0.0.0:4723
如果启动 Appium 服务时添加了参数,会在启动日志中展示,比如添加了 defaultCapabilities,启动日志也会显示出来。
Hogwarts $ appium --log-timestamp --log-no-colors --no-reset
2021-04-29 10:11:58:545 - [Appium] Welcome to Appium v1.17.0
2021-04-29 10:11:58:547 - [Appium] Non-default server args:
2021-04-29 10:11:58:547 - [Appium] logTimestamp: true
2021-04-29 10:11:58:547 - [Appium] logNoColors: true
2021-04-29 10:11:58:547 - [Appium] noReset: true
2021-04-29 10:11:58:548 - [Appium] Deprecated server args:
2021-04-29 10:11:58:548 - [Appium] --no-reset => --default-capabilities '{"noReset":true}'
2021-04-29 10:11:58:548 - [Appium] Default capabilities, which will be added to each request unless overridden by desired capabilities:
2021-04-29 10:11:58:548 - [Appium] noReset: true
2021-04-29 10:11:58:567 - [Appium] Appium REST http interface listener started on 0.0.0.0:4723
上面的日志创建了一个 Session,设置了 Capabilities 参数,以 JSON 格式告诉 AppiumServer 被测试设备的一些重要信息。
## Appium GET 请求的日志
Appium 是一个 REST 服务,接收 HTTP 请求,返回结果。Appium 服务端日志用 [HTTP] --> 和 [HTTP] <--展示了请求和返回的信息。
[HTTP] --> GET /wd/hub/status {}
[debug] [MJSONWP] Calling AppiumDriver.getStatus() with args: []
[debug] [MJSONWP] Responding to client with driver.getStatus()
result: {“build”:{“version”:“1.8.0-beta3”,“revision”:“30e7b45bdc5668124af33c41492aa5195fcdf64d”}}
[HTTP] <-- GET /wd/hub/status 200 121 ms - 126
“-->”代表发出 HTTP 请求,“<--”代表响应,中间是指令细节。[MJSONWP] 指使用 MJSONWP(Mobile JSON Wire Protocol 协议),调用 AppiumDriver.getStatus( )这个方法(无参),返回给客户端 result 信息,整个过程耗时 121 毫秒,传输了 126 个字节。
## 通过日志进行错误排查
利用日志可以非常容易的排查和定位问题,问题通常发生在 automation Session 之后,如果 Session 持续存在,错误也可能发生。
[HTTP] --> POST /wd/hub/session
…
[debug] [ADB] 1 device(s) connected
[debug] [ADB] Running ‘/home/user/Android/Sdk/platform-tools//adb’
with args: [“-P”,5037,“-s”,“ec8c4df”,“shell”,“am”,“force-stop”,
“io.appium.unlock”]
[debug] [AndroidDriver] Not cleaning generated files. Add
clearSystemFiles
capability if wanted.
[MJSONWP] Encountered internal error running command: Error:
Cannot stop and clear com.company.app. Original error: Error
executing adbExec. Original error: ‘Command ‘/home/user/Android
/Sdk/platform-tools//adb -P 5037 -s ec8c4df shell pm clear com.
company.app’ exited with code 1’; Stderr: ‘Error: java.lang.SecurityException:
PID 22126 does not have permission android.permission.CLEAR_APP_USER_DATA to
clear data of package com.company.app’; Code: ‘1’
at Object.wrappedLogger.errorAndThrow (…/…/lib/logging.js:63:13)
at ADB.callee$0 0 0 0 (…/…/…/lib/tools/adb-commands.js:334:9)
at tryCatch (/home/linuxbrew/.linuxbrew/lib/node_modules/appium/node_modules
/babel-runtime/regenerator/runtime.js:67:40)
at GeneratorFunctionPrototype.invoke [as _invoke] (/home/linuxbrew/.
linuxbrew/lib/node_modules/appium/node_modules/babel-runtime/regenerator
/runtime.js:315:22)
…
[HTTP] <-- POST /wd/hub/session 500 40811 ms - 557
Appium Driver 启动 Session ,清理 com.company.app 时发生错误。这个错误让我们知道两件事:“Appium 正在尝试做什么”,“哪里出错了”。
在这个例子中,Appium 尝试运行 adb 命令(adb shell am force-stop),adb 参数在错误信息中也有显示。发生了 Android 系统权限错误。此时,可以手动运行这个 adb 命令,查看错误是否可以重现。如果错误重现,可以通过错误类型定位问题。
这个例子只是众多错误中的一个,但它说明至关重要的一点,当错误发生时,日志可以提供更多的信息,如果没有完整的日志信息,对 Appium 排错难上加难。
## 改变日志输出的参数
下面的参数可以改变 Appium 服务端的日志行为:
--log-level:改变 Appium 日志显示级别。Appium 默认展示所有日志
,它有以下一些选项:'info', 'info:debug', 'info:info', 'info:warn', 'info:error', ...
--log-no-colors:关闭颜色,如果日志是彩色的,可能会出现奇怪的字符,比如"TODO: find the color",你可以用这个参数关闭颜色。
--log-timestamp:在日志前添加时间戳
展示如下:
2018-03-15 13:17:58:663 - [Appium] Welcome to Appium v1.8.0-beta3 (REV 30e7b45bdc5668124af33c41492aa5195fcdf64d)
2018-03-15 13:17:58:664 - [Appium] Non-default server args:
2018-03-15 13:17:58:665 - [Appium] logTimestamp: true
2018-03-15 13:17:58:732 - [Appium] Appium REST http interface listener started on 0.0.0.0:4723
###
⬇️ 点击“阅读原文”,提升测试核心竞争力!
[原文链接](https://mp.weixin.qq.com/s?__biz=MzU3NDM4ODEzMg==&mid=2247497874&idx=1&sn=68b58b1dcd7bdbe7fd78529b1a190c84&chksm=fd319a59ca46134f765ac83105ee37bf1abbb2c85566b7b0acf50f9c70e045b2d849dbfb72ee#rd)
获取更多相关资料+v~ ceshiren001
[获取更多技术文章分享](https://qrcode.ceba.ceshiren.com/link?name=article&project_id=qrcode&from=csdn2×tamp=1651204446)
Appium 是由 Node.js 来实现的 HTTP 服务,它并不是一套全新的框架,而是将现有的优秀的框架进行了集成,在 Selenium WebDriver 协议(JsonWireProtocol/Restful web service)的基础上增加了移动端的支持,使 Appium 满足多方面的需求。
官方提供更详细的 Appium 结构说明:https://appium.io/docs/en/contributing-to-appium/appium-packages/
## Appium 原理
不同平台(如 Android,iOS)采用不同的运行和交互方式。Appium 可以创建并管理多个 WebDriver Session 来和不同的平台交互。
以下几步可以编译构建 Appium 源码:
// clone appium 源码
git clone https://github.com/appium/appium.git
cd appium
// 安装项目的依赖
npm install
// 编译
npm run build
// 运行
node .
Appium lib 目录包含了核心代码。
![](https://img-blog.csdnimg.cn/img_convert/41d7f5f04d272818cef29a1e2e67c2a6.png)
比如 main.js 中包含 Appium 的启动语句:
async function logStartupInfo (parser, args) {
let welcome = Welcome to Appium v${APPIUM_VER}
;
let appiumRev = await getGitRev();
if (appiumRev) {
welcome += (REV ${appiumRev})
;
}
logger.info(welcome);
let showArgs = getNonD
修改为
async function logStartupInfo (parser, args) {
let welcome = hello v${APPIUM_VER}
;
let appiumRev = await getGitRev();
if (appiumRev) {
welcome += (REV ${appiumRev})
;
}
logger.info(welcome);
let showArgs = getNonD
编译后 Appium Server 的启动日志会发生改变:
![](https://img-blog.csdnimg.cn/img_convert/9cb26b0951cba7228be84d54e4fec618.png)
值得注意的是, Appium 本身不包含任何有关测试方法的代码,如果对底层进行修改和定制,请观看后续文章。
###
⬇️ 点击“阅读原文”,提升测试核心竞争力!
[原文链接](https://mp.weixin.qq.com/s?__biz=MzU3NDM4ODEzMg==&mid=2247497910&idx=1&sn=6174bb2126d41adfb55c15cef46fa253&chksm=fd319a7dca46136bb1becf050cd4a26c41483641bf566d00d208178faab4f91cc8ccfad3ff03#rd)
获取更多相关资料+v~ ceshiren001
[获取更多技术文章分享](https://qrcode.ceba.ceshiren.com/link?name=article&project_id=qrcode&from=csdn2×tamp=1651204446)
Appium 是由 Node.js 来实现的 HTTP 服务,它并不是一套全新的框架,而是将现有的优秀的框架进行了集成,在 Selenium WebDriver 协议(JsonWireProtocol/Restful web service)的基础上增加了移动端的支持,使 Appium 满足多方面的需求。
官方提供更详细的 Appium 结构说明:https://appium.io/docs/en/contributing-to-appium/appium-packages/
## Appium 原理
不同平台(如 Android,iOS)采用不同的运行和交互方式。Appium 可以创建并管理多个 WebDriver Session 来和不同的平台交互。
以下几步可以编译构建 Appium 源码:
// clone appium 源码
git clone https://github.com/appium/appium.git
cd appium
// 安装项目的依赖
npm install
// 编译
npm run build
// 运行
node .
Appium lib 目录包含了核心代码。
![](https://img-blog.csdnimg.cn/img_convert/3f47edecc1daac8d28914284c508a298.png)
比如 main.js 中包含 Appium 的启动语句:
async function logStartupInfo (parser, args) {
let welcome = Welcome to Appium v${APPIUM_VER}
;
let appiumRev = await getGitRev();
if (appiumRev) {
welcome += (REV ${appiumRev})
;
}
logger.info(welcome);
let showArgs = getNonD
修改为
async function logStartupInfo (parser, args) {
let welcome = hello v${APPIUM_VER}
;
let appiumRev = await getGitRev();
if (appiumRev) {
welcome += (REV ${appiumRev})
;
}
logger.info(welcome);
let showArgs = getNonD
编译后 Appium Server 的启动日志会发生改变:
![](https://img-blog.csdnimg.cn/img_convert/38c17c916a4ac03a4adb020508eefe7e.png)
值得注意的是, Appium 本身不包含任何有关测试方法的代码,如果对底层进行修改和定制,请观看后续文章。
###
⬇️ 点击“阅读原文”,提升测试核心竞争力!
[原文链接](https://mp.weixin.qq.com/s?__biz=MzU3NDM4ODEzMg==&mid=2247497910&idx=1&sn=6174bb2126d41adfb55c15cef46fa253&chksm=fd319a7dca46136bb1becf050cd4a26c41483641bf566d00d208178faab4f91cc8ccfad3ff03#rd)
获取更多相关资料+v~ ceshiren001
[获取更多技术文章分享](https://qrcode.ceba.ceshiren.com/link?name=article&project_id=qrcode&from=csdn2×tamp=1651124437)
Appium 是由 Node.js 来实现的 HTTP 服务,它并不是一套全新的框架,而是将现有的优秀的框架进行了集成,在 Selenium WebDriver 协议(JsonWireProtocol/Restful web service)的基础上增加了移动端的支持,使 Appium 满足多方面的需求。
官方提供更详细的 Appium 结构说明:https://appium.io/docs/en/contributing-to-appium/appium-packages/
## Appium 原理
不同平台(如 Android,iOS)采用不同的运行和交互方式。Appium 可以创建并管理多个 WebDriver Session 来和不同的平台交互。
以下几步可以编译构建 Appium 源码:
// clone appium 源码
git clone https://github.com/appium/appium.git
cd appium
// 安装项目的依赖
npm install
// 编译
npm run build
// 运行
node .
Appium lib 目录包含了核心代码。
![](https://img-blog.csdnimg.cn/img_convert/db15ebdb8b20085e615b348ce4034052.png)
比如 main.js 中包含 Appium 的启动语句:
async function logStartupInfo (parser, args) {
let welcome = Welcome to Appium v${APPIUM_VER}
;
let appiumRev = await getGitRev();
if (appiumRev) {
welcome += (REV ${appiumRev})
;
}
logger.info(welcome);
let showArgs = getNonD
修改为
async function logStartupInfo (parser, args) {
let welcome = hello v${APPIUM_VER}
;
let appiumRev = await getGitRev();
if (appiumRev) {
welcome += (REV ${appiumRev})
;
}
logger.info(welcome);
let showArgs = getNonD
编译后 Appium Server 的启动日志会发生改变:
![](https://img-blog.csdnimg.cn/img_convert/e6dd006d191d38c8890dfe194078eeb3.png)
值得注意的是, Appium 本身不包含任何有关测试方法的代码,如果对底层进行修改和定制,请观看后续文章。
###
⬇️ 点击“阅读原文”,提升测试核心竞争力!
[原文链接](https://mp.weixin.qq.com/s?__biz=MzU3NDM4ODEzMg==&mid=2247497910&idx=1&sn=6174bb2126d41adfb55c15cef46fa253&chksm=fd319a7dca46136bb1becf050cd4a26c41483641bf566d00d208178faab4f91cc8ccfad3ff03#rd)
获取更多相关资料+v~ ceshiren001
[获取更多技术文章分享](https://qrcode.ceba.ceshiren.com/link?name=article&project_id=qrcode&from=csdn2×tamp=1651124044)
## Appium 框架结构
Appium 是由多个子项目构成的,github 访问如下图:
![](https://img-blog.csdnimg.cn/img_convert/82409fa4700d0002e90fb82f72723ff9.png)
Appium 由 Appium 以及其它的工作引擎包括:appium-xcuitest-driver、appium-android-driver、appium-ios-driver、appium-uiautomator2-server、appium-base-driver 等组成。下载 Appium 这个项目进行分析,发现 Appium 有着非常复杂的目录结构,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EeOqfsrB-1651124068251)(https://ceshiren.com/uploads/default/original/3X/d/5/d5c1562f967194b84ce4c26f9f3feef679ae8da3.png)]
其中重要的目录如下:
项目中有个文件 package.json ,这个文件是项目的描述文件。对项目或者模块包的描述,比如项目名称,项目版本,项目执行入口文件,项目贡献者等等。npm install命令会根据这个文件下载所有依赖模块,查看这个文件可以看到如下的信息:
“dependencies”: {
“@babel/runtime”: “^7.6.0”,
“appium-android-driver”: “^4.20.0”,
“appium-base-driver”: “^5.0.0”,
“appium-espresso-driver”: “^1.0.0”,
“appium-fake-driver”: “^0.x”,
“appium-flutter-driver”: “^0”,
“appium-ios-driver”: “4.x”,
“appium-mac-driver”: “1.x”,
“appium-support”: “2.x”,
“appium-tizen-driver”: “^1.1.1-beta.4”,
“appium-uiautomator2-driver”: “^1.37.1”,
“appium-windows-driver”: “1.x”,
“appium-xcuitest-driver”: “^3.0.0”,
…
},
dependencies 表示此模块依赖的模块和版本信息。从这里面可以看到它依赖很多 driver ,比如 appium-android-driver、appium-base-driver、appium-espresso-driver、appium-ios-driver、appium-uiautomator2-driver 等等。下面我们会根据 appium-uiautomator2-driver 重点对 Android 测试驱动的源码进行分析。
## appium-uiautomator2-server
appium-uiautomator2-server 是针对 UiAutomator V2 提供的服务,是一个运行在设备上的 Netty 服务器,用来监听指令并执行 UiAutomator V2 命令。
早期版本 Appium 通过 appium-android-bootstrap 实现与 UiAutomator V1 的交互,UiAutomator2 修复了 UiAutomator V1 中遇到的大多数问题,最重要的是实现了与 Android 系统更新的分离。
Appium 底层执行 Android 测试真正的工作引擎是一个 JAVA 项目 appium-uiautomator2-server。可以将这个项目克隆到本地,使用 Android Studio 工具或者其它的 JAVA 项目 IDE 工具打开这个项目。
## appium-uiautomator2-server启动
从 README 文件可以看到启动服务的方式:
Starting server
push both src and test apks to the device
and execute the instrumentation tests.
adb shell am instrument -w
io.appium.uiautomator2.server.test/
androidx.test.runner.AndroidJUnitRunner
找到 AppiumUiAutomator2Server.java 这个文件,如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tl8ED8mX-1651124073837)(https://ceshiren.com/uploads/default/original/3X/f/b/fb6b297985ec85a66aed16a3f3e7fdceb9145488.png)]
startServer( ) 方法就是它的启动入口函数。这个函数里面调用了 ServerInstrumentation 类里面的 startServer( ) 方法。如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qr4fINbV-1651124075010)(https://ceshiren.com/uploads/default/original/3X/d/1/d105c271da3634ef34d31c8b6781b8e9476fd17f.png)]
startServer( ) 方法会创建一个新的线程来处理一系列的逻辑。
## AppiumServlet解析
AppiumServlet 是一个典型 HTTP 请求的处理协议。使用 AppiumServlet 来管理请求,并将 Driver 发过来的请求转发给对应 RequestHandler,它会监听下面的 URL
…
register(postHandler, new FindElement(“/wd/hub/session/:sessionId/element”));
register(postHandler, new FindElements(“/wd/hub/session/:sessionId/elements”));
…
当这些 URL 有请求过来,AppiumServlet 会对它执行相关的处理。比如查找元素、输入、点击等操作。以查找元素为例,实现类里可以看到下面一段代码:
…
final String method = payload.getString(“strategy”);
final String selector = payload.getString(“selector”);
final String contextId = payload.getString(“context”);
…
通过这三个属性“strategy”、“selector”、“context” 来定位元素。在 Appium 对应的日志中可以看到这个操作。
2020-04-08 10:42:37:928 [HTTP] --> POST /wd/hub/session/f99fe38b-445b-45d2-bda0-79bf12e8910e/element
2020-04-08 10:42:37:929 [HTTP] {“using”:“xpath”,
“value”:“//[@text=“交易”]“}
2020-04-08 10:42:37:930 [W3C (f99fe38b)] Calling
AppiumDriver.findElement() with args: [“xpath”,”//[@text=“交易”]”,“f99fe38b-445b-45d2-bda0-79bf12e8910e”]
…
2020-04-08 10:42:37:931 [WD Proxy] Matched ‘/element’ to
command name ‘findElement’
2020-04-08 10:42:37:932 [WD Proxy] Proxying [POST /element] to
[POST http://127.0.0.1:8200/wd/hub/session/
0314d14d-b580-4098-a559-602559cd7277/element]
with body: {“strategy”:“xpath”,“selector”:
“//*[@text=“交易”]”,“context”:“”,“multiple”:false}
…
2020-04-08 10:42:39:518 [W3C (f99fe38b)] Responding
to client with driver.findElement()
result: {“element-6066-11e4-a52e-4f735466cecf”:
“c57c34b7-7665-4234-ac08-de11641c8f56”,
“ELEMENT”:“c57c34b7-7665-4234-ac08-de11641c8f56”}
2020-04-08 10:42:39:519 [HTTP] <-- POST /wd/hub/session/f99fe38b-445b-45d2-bda0-79bf12e8910e/element 200 1590 ms - 137
上面代码,定位元素的时候会发送一个 POST 请求,Appium 会把请求转为 UiAutomatorV2 的定位,然后转发给 UiAutomatorV2。
## 扩展功能
在 FindElement.java 中实现了 findElement( ) 方法,如下图:
private Object findElement(By by) throws UiAutomator2Exception, UiObjectNotFoundException {
refreshAccessibilityCache();
if (by instanceof ById) {
String locator = rewriteIdLocator((ById) by);
return CustomUiDevice.getInstance().findObject(androidx.test.uiautomator.By.res(locator));
} else if (by instanceof By.ByAccessibilityId) {
return CustomUiDevice.getInstance().findObject(androidx.test.uiautomator.By.desc(by.getElementLocator()));
} else if (by instanceof ByClass) {
return CustomUiDevice.getInstance().findObject(androidx.test.uiautomator.By.clazz(by.getElementLocator()));
} else if (by instanceof By.ByXPath) {
final NodeInfoList matchedNodes = getXPathNodeMatch(by.getElementLocator(), null, false);
if (matchedNodes.isEmpty()) {
throw new ElementNotFoundException();
}
return CustomUiDevice.getInstance().findObject(matchedNodes);
}
…
}
findElement( ) 方法具体的提供了 ById、ByAccessibilityId、ByClass、ByXpath 等方法,可以扩展这部分功能,如果将来引申出来一些功能,比如想要通过图片、AI 定位元素,可以在上面的 findElement( ) 方法里面添加 else if (by instanceof ByAI) 方法,来创建新类型ByAI并且增加功能的实现。比如未来新增了 AI 来定位元素的功能,可以使用 AI 的插件(基于 nodejs 封装的一个插件)test.ai 插件(https://github.com/testdotai/appium-classifier-plugin)
用法:
driver.find_element(‘-custom’, ‘ai:cart’);
## 项目构建与apk安装
完成代码的修改之后需要重新编译生成相应的 apk 文件,并放到 Appium 对应的目录下。
Android Studio -> 项目 Gradle -> appium-uiautomator2-server-master -> Task-other 下。
分别双击 assembleServerDebug 与 assembleServerDebugAndroidTest 即可完成编译,编译完成会在目录下生成对应的两个 apk 文件。
- assembleServerDebugAndroidTest.apk
构建后 apk 所在目录:app/build/outputs/apk/androidTest/server/debug/appium-uiautomator2-server-debug-androidTest.apk 这个 apk 是个驱动模块,负责创建会话,安装 UiAutomator2-server.apk 到设备上,开启 Netty 服务。
- assembleServerDebug
构建后 apk 所在目录:app/build/outputs/apk/server/debug/appium-uiautomator2-server-v4.5.5.apk,这是服务器模块,当驱动模块初始化完毕,服务器就会监听 PC 端 Appium 发送过来的请求,将请求发送给真正底层的 UiAutomator2。
另外,也可以使用命令来进行构建:
gradle clean assembleE2ETestDebug assembleE2ETestDebugAndroidTest
将编译完成的 APK,覆盖 Appium 目录下对应的 APK 文件。需要先使用命令查找 Appium 安装目录下的 Uiautomator server 对应的 APK,MacOS 操作命令如下:
find /usr/local/lib/node_modules/appium -name “uiautomator.apk”
使用上面的命令会发现关于 Uiautomator 的两个 apk 文件,如下:
$ find /usr/local/lib/node_modules/appium -name
“uiautomator.apk”
/usr/local/lib/node_modules/appium/node_modules
/appium-uiautomator2-server/apks/
appium-uiautomator2-server-v4.5.5.apk
/usr/local/lib/node_modules/appium/
node_modules/appium-uiautomator2-server
/apks/appium-uiautomator2-server-debug-androidTest.apk
将编译好的 APK 替换这个目录下的 APK 即可。
客户端会传递 Desired Capabilities 给 Appium Server 创建一个会话,Appium Server 会调用 appium-uiautomator2-driver 同时将 UiAutomator2 Server 的两个 apk 安装到测试设备上(也就是上面生成的两个 apk 文件)。
###
⬇️ 点击“阅读原文”,提升测试核心竞争力!
[原文链接](https://mp.weixin.qq.com/s?__biz=MzU3NDM4ODEzMg==&mid=2247498010&idx=1&sn=cb4bfbca5f979728d036e5cc9496a9ff&chksm=fd319bd1ca4612c7ae73a6a300899c8fefc3e0767dbd6edbd564a9914ae01f11421f99b84201#rd)
获取更多相关资料+v~ ceshiren001
[获取更多技术文章分享](https://qrcode.ceba.ceshiren.com/link?name=article&project_id=qrcode&from=csdn2×tamp=1651124044)