iOS 测试 iOS WebView/H5 调试新姿势

Eason for Sonic云真机系列 · 2022年10月14日 · 最后由 Eason 回复于 2022年10月18日 · 8426 次阅读
本帖已被设为精华帖!

前言

Sonic 平台周边生态 sib 推出新功能 webinspector 啦!给 iOS 进行 H5 测试带来什么不一样的体验呢?往下继续查看吧

sib 使用文档

iOS web 测试基础原理浅谈

当前的主流 H5 调试,基本上都是基于浏览器开放的 debug ws 服务来进行的。我们通过连接这些 ws,然后发送对应的协议过去,即可达到 debug 的目的,例如 iOS 获取 elements,则需要按照协议通过 ws 发送 getDocument 方法到 webkit 里面,等待 ws server 返回对应的 element 信息。iOS 的 webkit protocol 详细可参考:WebKit/Source/JavaScriptCore/inspector/protocol at main · WebKit/WebKit · GitHub ,里面通过划分域的形式,已经将协议分为主要的 20-30 个文件。

如何开启 iOS web debug 服务?

不同于安卓只需要简单的去开发者模式里开启 webview 的 debug 模式,iOS 由于其封闭性,开启 web debug 非常麻烦。我们需要发送相关的 DTX 协议给 iOS 内置的 com.apple.webinspector(参考:sonic-gidevice/webinspector.go at main · SonicCloudOrg/sonic-gidevice · GitHubsonic-ios-bridge/src/webinspector at main · SonicCloudOrg/sonic-ios-bridge · GitHub )。

大体流程如下:通过 gidevice 启动相关的 webinspector server 方法,随后 DTX 发送对应的connect id到 webinspector,

这时候会返回对应的 DTX 信息,我们会根据 DTX 信息的 case 标志 (Selector 参数) 进行 webinspector client 的初始化处理。

该过程中会得到当前设备中的 webkit*应用 pid 和 base page 信息(根据一些技术文章,如果 iOS 的应用有 developer 证书,则可以开启 H5 调试,后续开发维护时会进行验证其真实性)。根据这些 pid 和 page 信息,当需要对某个 webkit 应用进行 web debug 时,创建一个senderid,并将其发送到 webinspector 中,让 webkit 开启 debug 服务*,我们只需要发送相关的协议信息就行。

协议兼容

虽然 iOS 的 webkit inspector 是发展最早的一个网页调试器,但是由于 iOS 的封闭性和其他的一些因素,后续的其他内核的浏览器调试并没有使用 iOS 的 webkit 调试协议,基于易用性考虑,sonic 参考google/ios-webkit-debug-proxyRemoteDebug/remotedebug-ios-webkit-adapter 这两个项目,用 golang 重写了一遍,只需要使用 sib 的 webinspector adapter 模式,即可通过 chrome devtool 简单调试 iOS 的 safari。核心思路是 sib 将发送协议信息这个关键步骤做成 ws 服务,采用双向代理的模式,通过SonicCloudOrg/sonic-ios-webkit-adapter 拦截 iOS webkit 调试协议和 Chrome DevTools Protocol 协议之间的特异方法,将其转换成双方可接受的调试协议和返回结果

案例

如果我们需要获取当前页面下导航栏中的历史信息时,Chrome DevTools Protocol 的做法是 ws 里发送 Page 域中的 getNavigationHistory 方法到当前调试的应用中,等待返回的结果就行。比较可惜的是,这个方法直接发送到 iOS webkit 中,iOS webkit 会返回信息告知并没有这个方法,不过 iOS webkit 可以通过曲线救国的方式达到类似的效果。首先,我们先看 Chrome DevTools Protocol 中 getNavigationHistory 的返回信息是什么(参考:Chrome Devtools Protocol

{
    "id": "TransitionType",
    "description": "Transition type.",
    "type": "string",
    "enum": [
        "link",
        "typed",
        "address_bar",
        "auto_bookmark",
        "auto_subframe",
        "manual_subframe",
        "generated",
        "auto_toplevel",
        "form_submit",
        "reload",
        "keyword",
        "keyword_generated",
        "other"
    ]
}

{
    "name": "getNavigationHistory",
    "description": "Returns navigation history for the current page.",
    "returns": [
        {
            "name": "currentIndex",
            "description": "Index of the current navigation history entry.",
            "type": "integer"
        },
        {
            "name": "entries",
            "description": "Array of navigation history entries.",
            "type": "array",
            "items": {
                "$ref": "NavigationEntry"
            }
        }
    ]
}


{
    "id": "NavigationEntry",
    "description": "Navigation history entry.",
    "type": "object",
    "properties": [
        {
            "name": "id",
            "description": "Unique id of the navigation history entry.",
            "type": "integer"
        },
        {
            "name": "url",
            "description": "URL of the navigation history entry.",
            "type": "string"
        },
        {
            "name": "userTypedURL",
            "description": "URL that the user typed in the url bar.",
            "type": "string"
        },
        {
            "name": "title",
            "description": "Title of the navigation history entry.",
            "type": "string"
        },
        {
            "name": "transitionType",
            "description": "Transition type.",
            "$ref": "TransitionType"
        }
    ]
}

由返回结构可知,最重要的是 url 和 titile(其他信息可自定义生成),所以思路可以这样:

通过中间层拦截到这个特异性的方法,然后将这个方法替换成 iOS webkit protocol 下 Runtime 域的 evaluate 方法(evaluate 方法的使用说明参考:iOS webkit protocol Runtime),发送 window.location.href,获取全局 windows 对象下的 location.href 结果,然后再次使用 Runtime 域 evaluate 方法,发送 window.title,获取全局 windows 对象下的 title 结果,然后按照 getNavigationHistory 的返回结构组合获取到的这些信息,再返回到 devtool 中。

大体设计

不同调试协议之间的API 差异 概览

在自己项目应用 API 兼容库

go get -u github.com/SonicCloudOrg/sonic-ios-webkit-adapter

应用方向

通过 sib 的 webinspector adapter 功能,不只是 sonic 的 H5,任何基于 Chrome DevTools Protocol 开发的相关框架都可以用于操作 iOS 的 H5。目前 sonic 持续维护 SonicCloudOrg/sonic-ios-webkit-adapter 项目,用作 iOS H5 自动化的底层框架,如 auto touch、性能采集等,以及提供给前端一个开源易用的 iOS webdebug tool,而如果不使用 adapter,则是暴露原生的 iOS webkit ws 服务,可以通过 iOS webkit debug tool 工具进行调试(参考: webkit-webinspector ),在后续版本中,我们将会对devtool 进行二次开发,完善 sonic 的 H5 自动化前端。

在 sib 中使用

sib webinspector

启动后开启 chrome 的 debug 模式

chrome --remote-debug-port=9999

然后打开 chrome 的 devtools 工具,远程连接 webinspector 的调试 ws 就可以啦!

不过该功能当前还在继续完善,还有很多可以优化的空间,欢迎大家参与建设~

FAQ

  • Q:为什么用 golang 来写?和 ios-webkit-debug-proxy、remotedebug-ios-webkit-adapter 有什么区别吗?

  • F:我们使用 golang 的最大一个原因是其多个平台的兼容性,使用起来不需要额外的配置环境,而且以往的 iOS 相关 H5 测试大多需要搭配 xcode、Mac 环境,但是配合 sib 使用就可以做到脱离 Mac 跨平台使用,还摆脱了 nodejs 的依赖。虽然 ios-webkit-debug-proxy 项目也是跨平台的,但是其本身使用 C 语言,项目里有大量指针操作,对于没学过 C 语言的同学来说这个很折磨人。至于 remotedebug-ios-webkit-adapter 项目,除了摆脱 nodejs 以外,其最大问题是作者已经不再维护了,综合考虑之下决定使用 golang 重写这个项目,便于后期的维护和拓展。

  • Q:使用过程中某个 protocol 方法不支持怎么办?

  • F:目前项目只是初步完成,还有很多功能未完成,欢迎提 issue 或 pr,也欢迎熟悉 Chrome DevTools Protocol 协议和 iOS webkit debug protocol 协议的大佬加入一起维护项目。

  • Q:二次开发 chrome devtool 的原因是为什么?

  • F:因为在两个协议之间兼容时,sonic 很被动,无论是 Chrome DevTools Protocol 协议还是 iOS webkit debug protocol 协议发生更改,都导致 adapter 里需要花费大量的时间进行适配,我们需要提高自身的主动权。

文章摘自: 这里

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

@Lihuazhang 想申个精

新特性,抢先体验用起来

收藏 以后有空看

干饭狂人 回复

哈哈哈感谢支持

imath60 回复

这个实际应用会在平台端 2.0.0 发布的,目前是介绍相关原理

码一下

几个月前就在整理 IOS WebView 测试的东西, 用 Python 重写了 Appium 中与 IOS WebKit 交互解析过程. 如果当时有看到这个文章的话估计能少踩太多坑了😂

13楼 已删除
Eason #14 · 2022年10月18日 Author
chend 回复

可以一起维护 adapter 库哦~ 这样以后 ios webview 测试就可以更好啦

恒温 将本帖设为了精华贴 10月30日 19:29
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册