Appium 是由 Node.js 来实现的 HTTP 服务,它并不是一套全新的框架,而是将现有的优良的框架进行了集成,在 Selenium WebDriver 协定(JsonWireProtocol/Restful web service)的根底上减少了挪动端的反对,使 Appium 满足多方面的需要。
官网提供更具体的 Appium 构造阐明:https://appium.io/docs/en/con…
Appium 框架结构
Appium 是由多个子项目形成的,github 拜访如下图:
Appium 由 Appium 以及其它的工作引擎包含:appium-xcuitest-driver、appium-android-driver、appium-ios-driver、appium-uiautomator2-server、appium-base-driver 等组成。下载 Appium 这个我的项目进行剖析,发现 Appium 有着非常复杂的目录构造,如下图:
其中重要的目录如下:
我的项目中有个文件 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 这个文件,如下图:
startServer() 办法就是它的启动入口函数。这个函数外面调用了 ServerInstrumentation 类外面的 startServer() 办法。如下图:
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/…)
用法:
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 文件)。