ATX 利用 Chrome Dev Tools 进行 WebView 的测试 (初级版本)

codeskyblue · 2019年06月12日 · 最后由 codeskyblue 回复于 2023年03月09日 · 8096 次阅读
本帖已被设为精华帖!

前言

继上篇文章 Tencent FAutoTest 源码解读 写出来之后
这篇文章则是将其中的精华抽出,并引导你如何使用简短的代码,实现 FAT 的功能。

我们这次用雪球股票这个 App 当我们的测试项目,直接使用 Xposed 强行开启项目的 WebView Debug 模式,如果测试自己的 App,并且有开发在旁边也可以让他/她加句代码

if (0 != (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE)) {
    WebView.setWebContentsDebuggingEnabled(true);
}

在我之前写过的文章 Android WebView 研究笔记 有提到过 Xposed 这个应用

手动实现 WebView 自动化

打开 App,点击实盘交易 就进入了 WebView 界面,WebView 界面上的所有内容都不是原生控件了。

使用下面的命令找出雪球的 pid

$ adb shell ps | grep xueqiu
u0_a133   4433  566   1634988 264956 SyS_epoll_ 0000000000 S com.xueqiu.android
u0_a133   4510  566   1208020 78048 SyS_epoll_ 0000000000 S com.xueqiu.android:pushservice

然后再执行个命令

$ adb shell cat /proc/net/unix | grep --binary-file=text webview
0000000000000000: 00000002 00000000 00010000 0001 01 1433219 @webview_devtools_remote_4433

这里看到 Android 监听了 @webview_devtools_remote_4433 其中的 4433 对应的是雪球的 pid。(注:名字可能不会这么规范,可能是webview_devtools_remote_m6x_4433, 也可能是 browser_webview_devtools_remote_22091)
我们接着用 adb forward 命令将 webview 调试转发到本地。

$ adb forward tcp:5000 localabstract:webview_devtools_remote_4433
$ curl localhost:5000/json/version
{
   "Android-Package": "com.xueqiu.android",
   "Browser": "Chrome/62.0.3202.84",
   "Protocol-Version": "1.2",
   "User-Agent": "Mozilla/5.0 (Linux; Android 6.0.1; SM901 Build/MXB48T; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36",
   "V8-Version": "6.2.414.37",
   "WebKit-Version": "537.36 (@957d898f0f6e46cd9661d91d2bae899f34c1c4b6)",
   "webSocketDebuggerUrl": "ws://localhost:5000/devtools/browser"
}

通过这个信息我们也可以确定当前连接的 webview devtools 确实是雪球的。
接下来访问 localhost:5000/json/list这个地址,可以获取到 webview 所有打开的界面。(访问 localhost:5000/json 也可以)

webview 就是一个简化修改版的浏览器。每一个页面对应一个 tab。

$ curl localhost:5000/json/list
[ {
   "description": "{\"attached\":true,\"empty\":false,\"height\":1698,\"screenX\":0,\"screenY\":222,\"visible\":true,\"width\":1080}",
   "devtoolsFrontendUrl": "http://chrome-devtools-frontend.appspot.com/serve_rev/@957d898f0f6e46cd9661d91d2bae899f34c1c4b6/inspector.html?ws=localhost:5000/devtools/page/2c1c9bfc-e941-42a9-bfc1-0b1f2f73e553",
   "id": "2c1c9bfc-e941-42a9-bfc1-0b1f2f73e553",
   "title": "实盘交易",
   "type": "page",
   "url": "file:///data/user/0/io.va.exposed/virtual/data/user/0/com.xueqiu.android/files/com.xueqiu.android.h5/modules/broker/tradeHome.html",
   "webSocketDebuggerUrl": "ws://localhost:5000/devtools/page/2c1c9bfc-e941-42a9-bfc1-0b1f2f73e553"
} ]

因为 Chrome DevTools Protocal(CDP) 是基于 WebSocket 协议的,我用门 python websocket_client库来进行下交互。

import websocket
import json
from pprint import pprint

conn = websocket.create_connect("ws://localhost:5000/devtools/page/2c1c9bfc-e941-42a9-bfc1-0b1f2f73e553")
conn.write(json.dumps({"id": 1, "method": "Runtime.evaluate", "params": {"expression": "window.location.toString()"}}))
data = conn.recv()
pprint(data)
# 输出:{"id":1,"result":{"result":{"type":"string","value":"https://-- 一个很长的网址--"}}}

发送的消息格式跟jsonrpc 2.0协议几乎一模一样。
Github 上搜了搜,发现有专门针对 Chrome Devtools Protocol 写的库 https://github.com/fate0/pychrome,下面的代码用 pychome 库重写一下,并加入 XPath 获取元素的功能

JS 如何使用 XPath 参考了: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_using_XPath_in_JavaScript

import pychrome
import json

browser = pychrome.Browser("http://localhost:5000")
tab = None
for t in browser.list_tab():
    t.start()
    hidden = t.call_method("Runtime.evaluate", expression="document.hidden")["result"]["value"]
    if not hidden: # 页面处于激活状态
        tab = t
        break
    t.stop()
else:
    raise RuntimeError("no activated tab")

# 获取 按钮(天气)的中心坐标
ret = tab.call_method("Runtime.evaluate", expression="""
(function(){
    var xpath = '//*[contains(text(), "天气")]'
    var obj = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null);
    var button = obj.iterateNext();
    var rect = button.getBoundingClientRect()
    // [rect.left, rect.top, rect.right, rect.bottom]
    var x = (rect.left + rect.right)/2
    var y = (rect.top + rect.bottom)/2;
    return JSON.stringify([x, y])
})
"""

x, y = json.loads(ret['result']['value'])

# 执行点击操作
tab.Input.synthesizeTapGesture(x=x, y=y, duration=200, tapCount=1)
# 等价于 tab.call_method("Input.synthesizeTapGesture", x=x, y=y, duration=200, tapCount=1)

tab.stop()
tab.wait(5) # 等待thread进程退出

到这个地方,我们就实现了 通过 XPath 查找对应空间,然后点击的功能。继续扩展下,说不定就能弄出一个 webview 测试框架出来。

参考资料

总结

未完可能不待续。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 8 条回复 时间 点赞

明天睡醒再读一遍

thanksdanny 回复

就知道睡觉

思寒_seveniruby 将本帖设为了精华贴 06月13日 15:08

火钳刘明

6楼 已删除

expression="""
(function(){
var xpath = '//*[contains(text(), "天气")]'
var obj = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null);
var button = obj.iterateNext();
var rect = button.getBoundingClientRect()
// [rect.left, rect.top, rect.right, rect.bottom]
var x = (rect.left + rect.right)/2
var y = (rect.top + rect.bottom)/2;
return JSON.stringify([x, y])
})
"""

这块具体如何使用

codeskyblue webview 研究踩到的坑 中提及了此贴 09月10日 11:42
simple [精彩盘点] TesterHome 社区 2019 年 度精华帖 中提及了此贴 12月24日 22:32

这个项目目前还在继续吗

最近也在研究 webview,翻到这个帖子,感叹大佬的技术前瞻性。由于最近几年接触了 puppeteer,发现 Python 有 pyppeteer 版本(非官方,好久没迭代了),提供了 browser.connect() 方法可以直接连接 websocket,接下来就是直接用 puppeteer 提供的方法了,可以代替文中所说的 websocket_client 交互方式,亲测可用,代码如下:

ws = requests.get("http://localhost:9516/json/version").json()['webSocketDebuggerUrl']
browser = await connect(
    {
        'browserWSEndpoint': ws
    })
pages = await browser.pages()
logger.debug(await pages[0].title())
await pages[0].click('button.userspace-btn-edit')
Kk0t 回复

哈哈,竟然还有人看 2019 年的文章。现在貌似出来了一个 playwright

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册