我们知道混合应用是在原生应用里嵌套了H5页面,H5是在webview控件里的,那么我们在UI自动化时该如何定位webview里的元素呢?我们可以通过 uiautomatorviewer
将webview解析出来android能识别的组件,但是这种方式在不同的手机上有可能解析的不一致,导致自动化脚本不稳定,不推荐使用这种方法。另外我们可以手机端的webview页面映射到PC端,在PC端通过将chrome://insepect 查看页面元素,推荐使用,但是注意这里需要外网环境。
如下将分析一段appium的日志来分析webview的工作原理,文章尾部附有自动化脚本及完整日志:
解析:
-
获取上下文列表
服务端发送命令adb shell cat /proc/net/unix
获取域套接字列表。那什么是域套接字呢?
域套接字:是unix系统里进程与进程之间通信的一种方式。客户端想要与服务端想要连接,必须有共同的套接字和相应的服务端的端口号。套接字会一直处于监听状态,监听客户端发来的请求。
adb shell cat /proc/net/unix
是获取手机端的套接字,这些套接字是用来监听外界发来的请求
adb shell cat /proc/net/unix |grep webview
显示webview的套接字,其中套接字最后的数字就是进程ID
获取到webview的进程后,通过命令adb shell ps |grep 9986
查询出来该进程对应的应用
-
匹配chromedrvier
这里是将现存的chromediver进程杀掉,并查找可以匹配webview版本的chromedriver,如果没有相对应的版本此处会报错。 -
查看本地和手机端的tcp连接映射关系
adb forward --list
adb forward 命令是查看本地和手机端端tcp连接的映射关系
结果显示本地的57973端口和手机端的webview端口进行通信
adb forward tcp:8888 tcp:9999
建立本地8888端口和手机端9999端口的映射
adb forward --remove tcp:8888
删除8888端口
更多adb forward的使用方法参考:adb |grep forward
注意看:日志上是将这个映射关系给抹除了,目的是防止该进程没有被正常关闭时影响接下来的操作。 -
启动chromedriver
这里启动起来了chromedriver,占用的是8000端口(日志没截全,可以去看文章开头的全量日志)监听的本地的5037端口,为啥占用的是8000?在日志第303行,因为我们的脚本中没有指定端口,所以appium默认选择了一个空闲的8000端口。 -
创建session
这里的session是指的appium server和chromedriver之间通信的session, -
转发请求,将appium server的请求转发给chromedriver
Proxying [GET /wd/hub/session/b76bb00a-43bc-4155-bd6a-ccaa3eb865b2/source] to [GET http://127.0.0.1:8000/wd/hub/session/ed7fb83742cae3d1847485e4a02ad001/source] with body: {}
这条命令可以看出来,appium server对发到4723端口的操作,转发到了8000端口,并且session_id是appium. server 和chromedriver之间的session,本质上就是appium做了个代理,把请求进行了一次转发。
以上是我对appium 在做webview进行自动化测试时的总结,以上如有疏漏烦请各位指出,以期共同进步。
完整日志:
脚本及appium日志如下:
自动化脚本:
from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy
from appium.webdriver.common.touch_action import TouchAction
class TestDemoTwo:
def setup(self):
caps = {
"platformName": "android",
"deviceName": "test",
"appPackage": "io.appium.android.apis",
"appActivity": "io.appium.android.apis.ApiDemos",
"noReset": "true"
}
self.driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
self.driver.implicitly_wait(10)
def teardown(self):
pass
def test_webview(self):
view = "WebView"
self.driver.find_element(MobileBy.XPATH, "//*[@text='Views']").click()
# print(self.driver.contexts)
self.driver.find_element(MobileBy.ANDROID_UIAUTOMATOR,
f'new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text("{
view}").instance(0));').click()
# 打印上下文
print(self.driver.contexts)
# 定位元素及操作
self.driver.find_element(MobileBy.XPATH, '//*[@resource-id="i am a link"]').click()
print(self.driver.contexts)
# 切换上下文
self.driver.switch_to.context(self.driver.contexts[-1])
# self.driver.find_element(MobileBy.XPATH)
# self.driver.find_element(MobileBy.XPATH, "//*[contains(@text,'other')]").get_attribute("text")
print(self.driver.page_source)
appium日志:
2021-10-10 08:00:36:303 [Appium] Welcome to Appium v1.15.1
2021-10-10 08:00:36:306 [Appium] Non-default server args:
2021-10-10 08:00:36:307 [Appium] sessionOverride: true
2021-10-10 08:00:36:308 [Appium] logFile: appium1.log
2021-10-10 08:00:36:428 [Appium] Appium REST http interface listener started on 0.0.0.0:4723
2021-10-10 08:00:51:282 [HTTP] --> POST /wd/hub/session
2021-10-10 08:00:51:283 [HTTP] {
"capabilities":{
"firstMatch":[{
"platformName":"android","appium:deviceName":"test","appium:appPackage":"io.appium.android.apis","appium:appActivity":"io.appium.android.apis.ApiDemos","appium:noReset":"true"}]},"desiredCapabilities":{
"platformName":"android","deviceName":"test","appPackage":"io.appium.android.apis","appActivity":"io.appium.android.apis.ApiDemos","noReset":"true"}}
2021-10-10 08:00:51:288 [W3C] Calling AppiumDriver.createSession() with args: [{
"platformName":"android","deviceName":"test","appPackage":"io.appium.android.apis","appActivity":"io.appium.android.apis.ApiDemos","noReset":"true"},null,{
"firstMatch":[{
"platformName":"android","appium:deviceName":"test","appium:appPackage":"io.appium.android.apis","appium:appActivity":"io.appium.android.apis.ApiDemos","appium:noReset":"true"}]}]
2021-10-10 08:00:51:290 [BaseDriver] Event 'newSessionRequested' logged at 1633852851289 (16:00:51 GMT+0800 (中国标准时间))
2021-10-10 08:00:51:305 [Appium]
2021-10-10 08:00:51:305 [Appium] ======================================================================
2021-10-10 08:00:51:306 [Appium] DEPRECATION WARNING:
2021-10-10 08:00:51:306 [Appium]
2021-10-10 08:00:51:307 [Appium] The 'automationName' capability was not provided in the desired
2021-10-10 08:00:51:308 [Appium] capabilities for this Android session
2021-10-10 08:00:51:308 [Appium]
2021-10-10 08:00:51:309 [Appium] Setting 'automationName=UiAutomator2' by default and using the
2021-10-10 08:00:51:310 [Appium] UiAutomator2 Driver
2021-10-10 08:00:51:311 [Appium]
2021-10-10 08:00:51:311 [Appium] The next major version of Appium (2.x) will **require** the
2021-10-10 08:00:51:312 [Appium] 'automationName' capability to be set for all sessions on all
2021-10-10 08:00:51:313 [Appium] platforms
2021-10-10 08:00:51:314 [Appium]
2021-10-10 08:00:51:314 [Appium] In previous versions (Appium <= 1.13.x), the default was
2021-10-10 08:00:51:315 [Appium] 'automationName=UiAutomator1'
2021-10-10 08:00:51:315 [Appium]
2021-10-10 08:00:51:315 [Appium] If you wish to use that automation instead of UiAutomator2, please
2021-10-10 08:00:51:316 [Appium] add 'automationName=UiAutomator1' to your desired capabilities
2021-10-10 08:00:51:317 [Appium]
2021-10-10 08:00:51:317 [Appium] For more information about drivers, please visit
2021-10-10 08:00:51:317 [<