远程调试平台的建立
移动端 H5 页面经常运行在客户端 WebView 中。虽然是网页,想要开发调试却有额外的时间和操作开销。开发时,要分别准备 Android 或 iOS 的调试环境。测试发现问题后,开发还得根据截图、描述和零散日志尝试复现,较为困难。
设备不在身边,或者问题依赖特定账号、网络和客户端状态时,排查会更困难。页面一旦刷新或退出,DOM、Console、Network 和 JavaScript 运行时中的信息也可能随之消失。
我们围绕这个问题,基于开源库 Chii 二次开发,建设了一套 WebView 远程调试平台。业务页面接入后会主动连接服务器(称为目标页或target),开发者在浏览器中选择在线页面,打开调试(称为Client),再用 Chrome DevTools 查看和操作远端 WebView。
在基本调试链路之外,平台还提供设备管理、历史会话、日志与页面快照、连接诊断和补充的调试面板(如埋点、网络MOCK、JsBridge)。开发和测试可以围绕同一个页面现场协作。
一、项目背景与问题分析
1.1 设备平台差异带来的调试成本
传统 WebView 调试依赖系统提供的专属能力。
Android 侧通常要开启开发者模式和 USB 调试,应用本身也要允许 WebView 调试。连接设备后,开发者再通过 ADB 和 Chrome 选择目标页面。 iOS 侧需要先开启 Web 检查能力,连接并信任 Mac,然后从 Safari 开发菜单中选择页面。 另外鸿蒙设备也存在类似的问题。
有几个痛点:
- 调试设备通常需要在开发人员身边,且启动调试的流程复杂;
- Android 与 iOS 的配置和工具入口不同,流程不通用;
- 客户端与 WebView 必须开放对应调试能力;
- 异地测试设备、共享设备和偶发问题无法解决;
1.2 问题现场在协作链路中的信息损失
测试发现问题后,通常先整理现象、截图、录屏、账号、操作步骤和少量日志,再通过 Bug 单或企微同步给开发。开发拿到材料后,还要重新理解问题,准备相同版本和账号,构造数据与运行环境,最后才能尝试复现。
截图和文字适合记录现象,却无法完整承载动态运行状态。DOM 节点、控制台对象、请求时序、页面内存状态和客户端通信记录都可能在整理过程中丢失。页面退出或刷新后,原先现象也可能无法复现。因此会造成开发与测试之间多轮的补充沟通,很多时候还需要额外补齐一些信息。
二、建设目标与设计原则
目标很简单:让开发者在电脑上调试另一台设备中的 WebView。另外期望遵循以下的设计原则
2.1 设计原则
2.1.1 统一不同设备的调试入口
Android、iOS、鸿蒙以及其他客户端环境中的页面,都通过同一种方式接入服务。开发者在浏览器中选择目标页面,不再针对设备平台切换调试工具。
2.1.2 降低业务接入成本
调试能力需要按需启用,业务仓库不包含任何调试代码。关闭调试后,页面应恢复原始访问流程,避免调试逻辑影响到无关环境。同时,开启调试的流程也应当简单易用
2.1.3 管理多人、多页面和多设备
除了基础的连接能力外,开发者还要知道页面属于哪台设备、哪个环境、哪个调试任务,并能从大量在线目标中快速找到它。
2.1.4 保存问题现场
开发者无法实时介入时,平台需要保存页面、设备、日志和快照,使问题能够在事后继续分析。
2.1.5 支持持续扩展
基础 DevTools 面板主要解决 DOM、控制台、应用存储和网络等问题。埋点检查、接口模拟、客户端通信等场景需要专有能力。平台应允许新面板复用现有连接和调试界面,复用现有链路,扩展新的功能。
2.2 期望效果
2.2.1 远程调试对开发体验的改进
本地调试需要在开发者身边准备 Android 或 iOS 环境,按平台切换 ADB、Safari 开发菜单等工具和流程。远程调试抹除了这些差异。只需要打开电脑浏览器,打开服务页面、找到目标、点击“调试”即可进入 DevTools。PC上客户端或浏览器的业务页若想使用远程调试的能力,也可以直接接入。
2.2.2 远程调试对问题排查流程的改进
BUG 单仍记录问题、责任流转、处理进度和最终结论;而调试平台负责把设备上的真实运行现场开放给开发。
流程中减少了两段重复工作:测试不必人工整理完整调试材料,开发也不必根据转述重新准备设备和构造环境。而可以直接在远程排查问题现场。
三、平台能力介绍
3.1 WebView 实时远程调试
页面接入后,服务首页展示当前在线的 WebView。列表包含标题、URL、UA、环境、UID、IP等信息。开发者找到目标页面,点击“调试”,浏览器就会打开与该页面连接的 Chrome DevTools。
常用能力包括:
- 查看和修改 DOM、CSS;
- 查看 Console 日志,执行 JavaScript;
- 查看 fetch、XHR、WebSocket 等网络请求;
- 查看脚本资源、运行时对象和调用栈;
- 查看应用存储和 Cookie;
- 在设备页面与电脑之间双向传递调试命令和状态。
3.2 历史会话与问题现场回溯
平台为页面连接创建会话,保存URL、UA、截图、ConsoleLog、全局错误、IP、UID、时间等。
开发者可以在问题发生后查找对应会话,复制、筛选和下载日志;如果页面仍在线,也可以直接进入 DevTools。
3.3 房间化设备组织
平台提供“房间”作为设备分组机制。一个房间对应多个页面,开发者可以创建或加入房间,设备通过房间ID或扫码绑定。在线页面和历史会话都支持按当前房间过滤。
3.4 专项调试面板
在标准 DevTools 面板之外,增加了三类补充能力:
- Beacon 面板:这里的 Beacon 指埋点事件分析,用于从网络请求中识别上报事件并解析参数;
- Mock 面板:按规则拦截 fetch/XHR,并返回模拟的状态码、响应头、响应体或延迟;
- JSBridge 面板:JSBridge 是 WebView 页面调用客户端原生能力的通信桥,面板用于查看调用记录、Mock 返回结果和主动发起调用。
3.5 连接诊断、帮助与反馈
目标页面内还会新增一个调试浮窗,会展示脚本注入情况、连接情况、当前房间、设备ID和连接错误等。便于排查远程调试不可用的原因。
四、系统架构与技术
4.1 系统逻辑架构
系统由三个角色组成:
- 目标页面:真正运行在设备中的 WebView,建立与服务器的 WebSocket 连接,并在通道中传输消息
- 调试服务器:登记在线页面,接收开发者连接并配对,在双方之间转发调试消息;
- 开发者浏览器:先在管理页面选择目标,再打开 DevTools 进行实时调试。
这是一种“多页面—单一服务—多开发者”的结构。多个 WebView 可以同时在线,多个开发者也可以访问服务。各条调试通道互不干扰。
4.2 远程调试建立流程
一次调试连接按下面的顺序建立。后文将页面侧连接称为 Target,将 DevTools 侧连接称为 Client;两者表示连接角色。
- 页面加载调试运行时,生成一个
targetId。它是当前页面实例在服务中的唯一标识。 - 页面通过 WebSocket 连接服务器,同时上报 URL、标题、UA等一系列信息。服务器据此生成在线目标列表。
- 开发者打开服务管理前端,从列表中选择一个页面并点击“调试”。
- 浏览器打开 DevTools 前端,并把所选页面的
targetId带给服务器。 - 服务器找到对应的页面连接,把页面端与 DevTools 端配成一组。
- DevTools 发出的命令经服务器转给页面;页面执行后产生的结果和事件沿原路返回。
服务管理前端展示页面、组织设备并打开目标。DevTools 前端负责发送调试命令。服务器只查找目标并转发消息,DOM、网络请求等数据都由 WebView 内的调试运行时产生。
4.3 调试协议与基础框架选型
Chrome DevTools Protocol,简称 CDP,是 Chrome DevTools 与浏览器调试后端之间的通信协议。开发者在 DevTools 中选择 DOM 节点、执行 JavaScript 或查看网络请求,背后都会产生 CDP 命令或事件。
例如,读取当前页面地址可以通过 Runtime Domain 发送命令:
{
"id": 1,
"method": "Runtime.evaluate",
"params": {
"expression": "location.href"
}
}
CDP 按 Domain 组织能力:
| Domain | 作用 |
|---|---|
| DOM | 获取和更新页面节点 |
| CSS | 读取与修改样式 |
| Runtime | 执行 JavaScript、访问运行时对象 |
| Network | 查看请求和响应 |
| Console | 接收控制台消息 |
| Debugger | 管理脚本、断点和调用栈 |
一套远程调试系统需要页面采集与控制、通信协议、服务器通道和调试界面。如果全部从头实现,主要工作会集中在上面这些浏览器调试能力上。
项目选择Chii开源项目 作为基础。Chii 是一个开源的 Web 远程调试框架,已经具备页面连接、服务器中转和DevTools入口。页面侧由 chobitsu开源库 模拟CDP,浏览器侧复用 Chromium 开源的 DevTools 前端。
4.4 页面侧两部分
页面侧最容易混淆的是 target.js 和 chobitsu。它们运行在同一个 WebView 中,但职责不同。
target.js 是注入页面的调试运行时入口,主要负责“连接和管理”,由webpack构建而成,目标页会加载此脚本并运行。它的职责包括:
- 初始化页面侧各项能力;
- 生成并保存
targetId; - 建立 WebSocket,处理断开与重连;
- 上报 URL、标题、设备和房间等信息;
- 缓存连接建立前产生的日志;
- 安装页面调试浮窗和专项 Hook;
- 在服务器与 chobitsu 之间转发消息。
chobitsu 负责理解和执行 CDP。普通网页无法访问浏览器内部的调试后端,因此它会包装或替换部分页面 API,接入 DOM、网络和运行时能力。页面行为由此转换为 CDP 事件,DevTools 命令也能落实为真实的页面操作。
以 Network Domain 为例,chobitsu 包装 fetch、XMLHttpRequest 和 WebSocket。页面发起请求时,它生成 Network.requestWillBeSent;收到响应后,再生成 Network.responseReceived。DevTools 根据这些事件绘制请求列表。
DOM Domain 则需要维护真实节点与协议节点之间的映射。chobitsu 为页面节点分配 nodeId,使用 MutationObserver 监听变化。当 DevTools 请求节点树或修改属性时,它在 CDP 数据结构与真实 DOM 之间转换。
target.js 负责让页面稳定接入平台,chobitsu 负责让页面能够被 DevTools 调试。
调试服务器 ⇄ target.js ⇄ chobitsu ⇄ 业务页面
连接管理 CDP 适配
JavaScript模拟无法覆盖浏览器内核的全部能力。默认的 Network 实现主要记录请求,不能像内核级 Fetch Domain 那样暂停并替换响应。
4.5 调试服务器的两类服务
调试服务器内部可以分为实时通信和平台业务两部分。
实时通信通过 WebSocket 完成。服务端接收两类连接:
/target/{targetId} 页面侧连接
/client/{clientId}?target=targetId DevTools 侧连接
页面连接建立后进入在线目标表。DevTools 连接建立时会指定 targetId。ChannelManager 是服务端的连接管理模块,它根据该标识找到目标页面并完成配对。此后的标准 CDP 报文保持透传,服务器不解析具体内容。而项目自定义的协议消息会被服务端接收并处理,如绑定房间、截图上传、日志保存等
平台业务通过 REST API 完成,包括在线目标、房间、设备绑定、会话、日志、快照、认证和使用统计。需要跨连接或跨重启保存的数据会写入数据库。
服务器还负责静态资源分发,包括 target.js、服务管理前端和 DevTools 前端构建产物。
实时调试:页面 ⇄ WebSocket 中转 ⇄ DevTools
平台管理:管理前端 ⇄ REST API ⇄ Database
资源加载:设备或浏览器 → 静态资源服务
4.6 浏览器端的两套前端
浏览器端同时运行服务管理前端和 DevTools 前端。两者是独立应用,职责和通信方式也不同。
服务管理前端使用 React 和 TypeScript 实现,主要通过 REST API 访问服务器。有以下页面:在线列表、历史会话、房间、我的、设置、使用统计等。开发者在这里完成目标发现和平台操作。作为一个独立的React应用,可以灵活地新增功能,满足各种需求。
DevTools 前端基于 Chromium 开源的 devtools-frontend。项目在其中增加 chii_app 入口,用来读取 targetId 并建立 Client WebSocket。连接成功后,Elements、Console、Sources、Network 等标准面板照常工作,Beacon、Mock 和 JSBridge 也注册在这个调试应用中。
两套前端之间只有一个关联:管理前端选择目标后,使用包含 targetId 的地址打开 DevTools 前端。之后的实时调试不再经过管理前端。
4.7 技术全景
从左到右看,上图分别对应目标 WebView、调试服务器和开发者浏览器;从内到外看,则体现了组件的包含关系:
- 目标 WebView 中包含业务页面和注入的调试运行时,运行时内部再分为连接管理、CDP 适配、专项 Hook 和调试浮窗;
- 调试服务器内部包含 WebSocket 实时通信、REST 平台接口、SQL数据存储和静态资源分发;
- 浏览器侧包含服务管理前端和 DevTools 前端,前者负责找到目标,后者负责实际调试。
页面侧产生调试数据并执行命令,服务器维护连接和平台状态,浏览器侧提供目标管理与调试交互。
五、基于网络代理的无侵入接入方案
要求每个业务项目主动引入 target.js 会带来持续的代码改造和维护成本,调试 Hook 也可能进入无关环境。项目因此将接入逻辑放在代理层。
这里使用 Whistle。Whistle 是一款基于 Node.js 的 HTTP(S) 代理与调试工具,可以根据规则修改请求和响应。项目在它的插件机制上增加了调试服务配置与脚本注入能力。
设备访问业务页面时,请求先经过 Whistle。代理把请求转发给业务服务器,收到原始 HTML 后,再按域名、路径和内容类型匹配规则。命中规则时,它在 HTML 中追加调试服务地址和 target.js 标签,然后把改写后的响应返回 WebView。
业务服务器始终返回原始页面,业务仓库也不需要引入调试依赖。关闭规则后,代理不再改写 HTML,页面恢复原始加载。
在具体的实践中,客户端拥有全局环境切换工具,可通过选择已配置调试代理的环境,目标页面会自动加载调试运行时。
一段Whistle配置示例如下
```whistle.wm-chii/inspect.html
<script>
window.ChiiTitle = document.title || '暂无';
window.ChiiServerUrl = '{ServerHost}';
</script>
<script src='https://{ServerHost}/target.js'></script>
```/转义斜杠记得删除
*(匹配规则,匹配的URL会开启远程调试) htmlPrepend://`{whistle.wm-chii/inspect.html}` enable://safeHtml
六、目标管理与协作能力建设
基础远程链路解决了连接问题。多人开始使用后,目标识别、设备组织、现场保存和连接诊断逐渐成为主要工作。
6.1 在线目标识别与信息更新
真实项目中,大量 WebView 可能使用相同标题;单页应用切换路由后,初始 URL 也可能失去参考价值。页面在线并不意味着使用者能够迅速找到它。
目标列表逐步增加了一系列优化使用体验的小功能:
- 实时更新 URL、标题和 favicon;
- 按配置的URL匹配规则自动修改标题,便于识别
- Android、iOS、HarmonyOS、iPad、Win、Mac等平台识别;
- 环境(例如:xx测试环境、预发、正式等)显示与筛选,任意列筛选、排序和详情展开;
- URL 右键自动复制和页面快照预览。
6.2 房间分组与跨域设备绑定
多人多页面同时使用时,目标列表条目会很多,难以分清。房间为在线目标增加分组关系。开发者创建或加入房间后,设备通过输入ID或扫码加入,在线页面和历史会话只显示当前房间的数据。
6.3 会话、日志与页面快照持久化
每次页面连接都会创建 session。session 是一次目标页面连接的会话记录,包含页面、设备、房间、开始时间和结束时间。
注入端采集Console、window.error、unhandledrejection 和页面快照。日志参数可能包含 Error、DOM 节点、Map、Set、Date 和循环引用,因此需要定制序列化规则。连续重复项合并计数;DevTools 较晚连接时,服务端可以分批回放已有日志。
持久化使用数据库。管理端支持关键词和级别筛选、一键复制、下载、全屏查看,以及从在线会话快捷进入 DevTools。开发者没有在问题发生时在线,也可以根据已有现场继续分析。
6.4 页面侧连接诊断
远程调试链路经过脚本注入、页面运行时、WebSocket 和服务器。任一环节异常都可能表现为“列表里没有页面”。如果页面侧没有状态反馈,排查调试工具本身仍然也会耗费大量精力。浮窗会显示:
- WebSocket 状态、连接时长和重连次数;
- 最近关闭原因和错误信息;
- targetId、服务地址、页面地址和设备信息;
- 当前房间和近期连接事件;
- 主动重连、扫码绑定、诊断信息复制和快照上传。
七、DevTools 专项面板扩展
7.1 Beacon 面板:基于 Network 数据的埋点分析
Beacon 面板用于分析业务埋点。常见埋点通过 fetch 或 XHR 上报,这些请求已经被 chobitsu 转换成 Network 事件,因此面板可以直接订阅 DevTools 的 NetworkLog:
const networkLog = Logs.NetworkLog.NetworkLog.instance();
networkLog.addEventListener(Logs.NetworkLog.Events.RequestAdded, this.onRequest);
networkLog.addEventListener(Logs.NetworkLog.Events.RequestUpdated, this.onRequest);
面板按 URL 识别埋点请求,解析事件数组,再按事件名、参数和公共字段组织展示。这个方案只修改 DevTools 前端,不新增服务端接口,也不重复采集请求。
navigator.sendBeacon 不在 chobitsu 默认采集范围内,需要覆盖时,可以在页面运行时补充 Hook,并继续转换成标准 Network 事件。
7.2 Mock 面板:页面侧请求拦截与规则控制
Mock 面板用于模拟接口响应。它允许开发者配置 URL 匹配、状态码、请求头、请求体、响应头、响应体和延迟。
chobitsu 的 Network Hook 主要用于记录请求,无法暂停并替换响应,因此页面运行时增加了 fetch/XHR 包装。面板负责编辑规则、启停和展示命中记录,页面负责匹配规则并生成模拟响应。
页面端通过 window.__chiiMock 暴露规则管理接口,面板再借助 CDP 的 Runtime Domain 调用它:
await target.runtimeAgent().invoke_evaluate({
expression: 'window.__chiiMock.setRules(' + JSON.stringify(rules) + ')',
returnByValue: true,
});
Runtime.evaluate 已经能够完成规则下发、状态查询和清理,因此没有新增自定义 CDP Domain,减少了协议定义与生成代码的维护工作。
7.3 JSBridge 面板:客户端通信的采集与控制
JSBridge 调用不经过浏览器网络层,标准 CDP 中没有对应记录。
注入端代理 Bridge 调用入口,记录方法名、参数、回调、耗时和返回结果,并把能力组织在页面全局对象上。面板通过 Runtime 增量读取记录,同时提供 Mock、主动调用和诊断信息。
不同客户端的 Bridge 挂载方式可能不同,引用也可能被业务代码重新赋值。页面运行时需要环境探测和定期检查,面板则展示当前代理状态,使采集失败具备明确原因。
7.4 面板扩展的设计
| 场景 | 数据位置 | DevTools 面板 | 页面运行时 | 服务端 |
|---|---|---|---|---|
| Beacon | 已有 Network 数据 | 筛选、解析和展示 | 无改动 | 无改动 |
| Mock | fetch/XHR 请求 | 规则管理与命中列表 | 拦截并模拟响应 | 无改动 |
| JSBridge | 原链路中没有数据 | 展示、控制和诊断 | Hook 并保存调用记录 | 无改动 |
开发新面板前,先确认以下问题:
- 数据在哪里产生?
- DevTools 是否已经持有?
- 功能是只读分析,还是需要控制页面?
- 状态是否需要多人共享或长期保存?
这些会影响改动点,发生在面板、页面运行时还是服务端。可以避免重复采集,也能控制新功能的维护范围。
八、功能演进与反馈驱动迭代
平台在实际运行中,分阶段开放能力:
- 先提供实时 DevTools、历史会话、日志和页面快照,这些是基础功能,也是最好理解的部分,适合其他同学快速上手使用。
- 增加房间、扫码和设备绑定,解决多人环境中的目标查找,第二阶段才开放,也便于其他同学理解房间功能的意义。
- 增加 Beacon、Mock 和 JSBridge 面板,覆盖更多具体调试任务,
这期间持续完善页面体验、使用文档,提供帮助和反馈。建设期间共跟进 23 条用户反馈,从反馈确认到对应功能上线平均少于 2 天。迭代以小版本为主:先确认反馈对应的真实任务和阻塞点,完成开发与验证后尽快发布,再根据使用情况继续调整。快速、积极的响应和迭代,让平台不断打磨,满足其他开发同学的需求,也增加了大家的使用粘性
九、平台使用数据与流程效果
从 2026 年 7 月 20 日至 8 月 20 日,共 32 个自然日,其中 26 天产生用户行为。
| 指标 | 数值 |
|---|---|
| 累计用户 | 22 人 |
| 实际发起远程调试的用户 | 20 人 |
| 主动远程调试 | 472 次 |
| 至少跨 2 天重复使用的用户 | 15 人,68.2% |
| 查看历史会话 | 180 次,19 人使用 |
| 查看页面快照 | 182 次,13 人使用 |
| 查看会话日志 | 171 次,11 人使用 |
| 活跃房间 | 11 个 |
| 活跃日平均日活 | 3.85 人 |
| 单日活跃用户峰值 | 7 人 |
22 名用户中有 20 名实际发起过调试,15 名在不同日期重复使用。历史会话、日志和快照均有 170 次以上的查看记录,说明事后回溯已经进入实际排查流程。共记录了 11,957 个目标连接会话。
从流程效果看,远程调试统一了不同设备的调试入口,也让开发可以从在线页面或历史会话直接进入现场。测试仍然记录必要的问题信息,开发减少了根据转述理解问题、准备设备和重复构造环境的过程。
十、技术边界与后续可扩展方向
现有方案依赖 JavaScript Hook 模拟浏览器调试能力,无法覆盖浏览器内核的所有行为,这也是底层实现的固有限制。若从内核层面实现,可以覆盖更多行为,但开发难度也会更高,且需要侵入客户端Webview容器.
后续还可以继续完善:
- 指数退避重连、死连接探测和网络恢复触发;
- DevTools 自动重新附加与跨刷新状态恢复;
- 性能、存储、操作回放等调试能力;
- 设备、日志、请求和页面快照的结构化问题报告;
- 基于结构化调试数据的辅助分析。
可以考虑接入大语言模型辅助汇总信息、归类日志和关联线索,如果能补充需求背景、问题描述等,甚至可以让AI自动定位问题。
十一、总结
项目最初只想解决:让开发者在电脑上打开远端 WebView 的 DevTools。真正投入开发和运行后,如何让工具好用、易用也是很重要的关注点。能够真实解决痛点,响应用户反馈,这些才是让工具真正发挥作用的关键。
开源框架提供了协议和基础通道,二次开发主要改善使用体验,扩展功能,提升性能和可靠性,使基础链路最终演变为一个完整、成熟、系统化的平台。