返回博客

远程调试平台的建立

2026-08-246 min 阅读

移动端 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;两者表示连接角色。

  1. 页面加载调试运行时,生成一个 targetId。它是当前页面实例在服务中的唯一标识。
  2. 页面通过 WebSocket 连接服务器,同时上报 URL、标题、UA等一系列信息。服务器据此生成在线目标列表。
  3. 开发者打开服务管理前端,从列表中选择一个页面并点击“调试”。
  4. 浏览器打开 DevTools 前端,并把所选页面的 targetId 带给服务器。
  5. 服务器找到对应的页面连接,把页面端与 DevTools 端配成一组。
  6. DevTools 发出的命令经服务器转给页面;页面执行后产生的结果和事件沿原路返回。
sequenceDiagram participant P as 目标 WebView participant S as 调试服务器 participant M as 服务管理前端 participant D as DevTools 前端 P->>S: 建立 Target WebSocket,上报 targetId 和页面信息 S-->>M: 提供在线目标列表 M->>D: 选择目标,打开 DevTools 并传入 targetId D->>S: 建立 Client WebSocket,指定 targetId S->>S: 查找目标并配对两条连接 D->>S: 发送调试命令 S->>P: 转发命令 P-->>S: 返回执行结果或页面事件 S-->>D: 转发结果

服务管理前端展示页面、组织设备并打开目标。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 包装 fetchXMLHttpRequestWebSocket。页面发起请求时,它生成 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。

sequenceDiagram participant W as 设备 WebView participant P as Whistle 代理 participant B as 业务服务器 participant D as 调试服务器 W->>P: 请求业务页面 P->>B: 转发原始请求 B-->>P: 返回原始 HTML P->>P: 匹配规则并追加 target.js P-->>W: 返回改写后的 HTML W->>D: 加载 target.js D-->>W: 返回调试运行时代码 W->>D: 初始化并建立远程连接

业务服务器始终返回原始页面,业务仓库也不需要引入调试依赖。关闭规则后,代理不再改写 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.errorunhandledrejection 和页面快照。日志参数可能包含 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 并保存调用记录 无改动

开发新面板前,先确认以下问题:

  1. 数据在哪里产生?
  2. DevTools 是否已经持有?
  3. 功能是只读分析,还是需要控制页面?
  4. 状态是否需要多人共享或长期保存?

这些会影响改动点,发生在面板、页面运行时还是服务端。可以避免重复采集,也能控制新功能的维护范围。

八、功能演进与反馈驱动迭代

平台在实际运行中,分阶段开放能力:

  1. 先提供实时 DevTools、历史会话、日志和页面快照,这些是基础功能,也是最好理解的部分,适合其他同学快速上手使用。
  2. 增加房间、扫码和设备绑定,解决多人环境中的目标查找,第二阶段才开放,也便于其他同学理解房间功能的意义。
  3. 增加 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。真正投入开发和运行后,如何让工具好用、易用也是很重要的关注点。能够真实解决痛点,响应用户反馈,这些才是让工具真正发挥作用的关键。

开源框架提供了协议和基础通道,二次开发主要改善使用体验,扩展功能,提升性能和可靠性,使基础链路最终演变为一个完整、成熟、系统化的平台。


评论

0/1000