# Gateway Linux Xvfb:规划、设计与验收 > 状态:设计草案,尚未实现、尚未通过运行验收。现状核对基线:`main@977e541`。 > 已确认范围:仅编写文档;保留 Docker,新增 Linux 物理机通过 Xvfb 虚拟屏幕启动浏览器。Linux 真实桌面、Windows/macOS 不在本次范围。`XFVB` 按 `Xvfb` 理解。 > 本文补充 [plan01](../plan01.md) 的环境运行能力,不替代平台业务验收;现有部署步骤仍以 [deployment](../deployment.md) 为准。下文配置、接口增量和时限均是待实现设计,不是当前可用功能。 ## 1. 目标与完成标准 同一套 Python gateway 能管理 Docker 浏览器和 Linux 本机浏览器;本机浏览器仅通过 Xvfb 运行,不依赖显示器或图形登录会话。使用者仍从环境页面创建、启动、停止和回收,不能靠管理员手工启动 Chrome 后填写 CDP 地址冒充完成。 交付分为两个独立结论: - **文档完成**:范围、现状差距、设计选择、接口影响、实施顺序、逐项验收步骤与失败判定完整;相对链接有效,未把目标描述成现有能力。 - **功能完成(后续开发)**:Docker 回归通过,Linux 本机 Xvfb 完成创建到回收的真实闭环;人工登录、会话保存、并发与故障恢复均有证据;业务接口复用通过,不以 Mock 或健康接口代替浏览器与平台证据。 首轮验收基线为 Linux x86_64、Python 3.12+、可运行受控 Chromium 的发行版;测试记录必须填写发行版、内核、systemd、X server 和浏览器精确版本。ARM、原生 Wayland 启动、跨宿主机 Profile 搬迁不在本轮。 部署支持一个控制面中 Docker 环境和本机 Xvfb 环境并存;同一个 Linux 本机 gateway 下多个 Xvfb 环境可同时运行。单个 gateway 部署实例选择一种运行后端,不在同一进程内混管 Docker 和宿主机进程。 ## 2. 当前能力与缺口 | 核对位置 | 当前实现 | 本次目标的缺口 | | --- | --- | --- | | `cmd/docker_gateway/gateway.py` 的 `Gateway`、`run` | 生命周期围绕 DockerClient、容器标签和网络代际;入口构造 DockerClient | 没有本机浏览器的创建、启停、回收、进程归属管理 | | 同文件 `external_cdp` / `BROWSER_CDP_URL` | 指定一个外部浏览器地址与 alias;列表返回 `external`;平台读取/动作可连接该浏览器 | 是接入已有浏览器,不负责启动;不能作为托管本机环境的实现,也不能用固定 runtime ID 证明新进程身份 | | `docker/browser-wrapper/docker-entrypoint.sh` | 容器内启动固定 `:99` Xvfb、x11vnc、CDP 转发,再启动 Chromium | 固定显示号仅在容器隔离下适用;不能原样搬到多环境宿主机;现有入口不等于本机服务 | | `cmd/control-plane/hub.go`、`internal/hub` | 创建参数、版本管理、运行识别、租约与清理包含镜像/容器/网络假设 | 需要明确本机运行信息,不能用假镜像、假 Docker network ID 填字段 | | `cmd/docker_gateway/douyin.py`、`xiaohongshu.py` | 浏览器动作通过受控 CDP 连接执行,含账号与运行代际核验 | 应复用业务逻辑,只替换受控浏览器定位与生命周期,不复制平台连接器 | 特别说明:现有 `external_cdp` 列表的 `proxy_ready=True` 并不能证明本机启动或代理就绪;`/healthz` 可用也不能证明浏览器存在。 ## 3. 方案选择 | 方案 | 优点 | 缺点 | 结论 | | --- | --- | --- | --- | | 只使用外部 CDP 接入 | 代码少,适合临时诊断 | 不拥有生命周期,无法保证回收与会话独占 | 不满足需求 | | 每个 gateway 选择 Docker 或本机后端;本机仅使用 Xvfb | 部署边界明确,可复用现有业务与控制面;本机不依赖 Docker | 需调整生命周期契约与本机进程管理 | **推荐,本文采用** | | 一个 gateway 进程同时管理容器和本机进程 | 注册入口少 | 同进程权限、依赖、故障边界混杂,没有当前必要性 | 不采用 | 不增加插件框架、调度集群、消息队列或另一套 HTTP 服务。用现有 Python HTTP 服务承载路由;内部按启动配置选择两个明确的执行实现,仅提取真正共享的生命周期操作与浏览器地址解析。 ### 3.1 成熟模式与采用边界 - Playwright 的 headed Linux 测试使用 Xvfb:虚拟屏幕承载正常有界面浏览器,不等于 `headless`;不自动获得可供人操作的远程桌面。 - Chromium 用独立 `user-data-dir` 保存 Profile;同一个目录不能被多个浏览器实例同时使用,不能拿用户日常 Chrome 默认目录作为托管 Profile。 - Xvfb 提供 X server;以实际 X client 连接确认就绪,而不是只看进程、socket 文件或固定等待。浏览器的 DISPLAY 与必要授权由本环境 Xvfb 启动流程提供,不继承宿主机桌面会话。 - systemd 的服务/cgroup 用于托管本机进程树和重启后的归属核验;不以 PID 文件或进程名匹配作为终止依据。 - 仅借鉴公开文档中的运行模式,不复制上游代码。采用的资料链接与许可证边界见第 10 节。 ## 4. 运行与配置设计 ```text 控制面(Go,环境/账号/策略/运行记录) ├─ Docker gateway(Python,现有部署) → 容器 → Xvfb + 浏览器 └─ 本机 gateway(同一 Python 服务代码,Linux 用户服务) ├─ 环境 A:专属进程组/cgroup + Xvfb + 浏览器 + 人工操作入口 └─ 环境 B:专属进程组/cgroup + Xvfb + 浏览器 + 人工操作入口 ``` Docker gateway 保持现有部署及 socket 归属;本机 gateway 不要求 Docker socket、镜像或 Docker 网络存在。宿主机无 Docker daemon 时,本机模式也必须完整启动。控制面通过注册地址访问 gateway;部署需验证容器到宿主机的实际连通地址,不能把控制面容器内的 `127.0.0.1` 当成宿主机。 ### 4.1 拟新增配置 所有配置先校验再创建目录、显示服务或浏览器;未知模式、缺少必要字段直接报错,不尝试切换另一模式。 | 配置/字段 | 所属层 | 规则 | | --- | --- | --- | | `BROWSER_RUNTIME=docker\|native` | gateway 启动配置 | 显式选择;Docker 现有部署在实现时写明 `docker` | | 本机浏览器版本 → 绝对可执行文件路径 | 本机 gateway 配置 | 有限版本表;文件存在、可执行、版本可探测;API 不能指定任意命令或 shell 文本 | | 本机 Profile 根目录、运行状态目录 | 本机 gateway 配置 | 绝对路径、目录可写;Profile 长期保存,临时状态与其分离;禁止路径越界 | | Xvfb 屏幕参数 | 本机 gateway 配置 | 初始固定为 `1920x1080x24`;确有不同尺寸需求再扩展环境参数 | 环境选择 gateway 后由 gateway 能力决定运行后端,不允许请求声称 `native` 却发给 Docker gateway。本机版本来自部署者明确安装的受控浏览器,版本与二进制校验值进入验收记录;不因启动失败自动下载替代浏览器,不假设普通 Chrome 能等价支持现有指纹参数。 本机显示固定使用 Xvfb,不新增显示模式选择参数。DISPLAY 与必要的 Xauthority 由 gateway 为每个实例生成、传递和清理,不读取宿主机图形会话作为替代。 ### 4.2 Xvfb - 每个运行实例独立 Xvfb,动态分配显示号;采用 X server 支持的分配机制并验证目标发行版能力,不扫描出一个空闲号后无保护地占用。 - 启动顺序:运行锁和 Profile 锁 → 持久保存启动归属记录 → 创建环境 unit → Xvfb → X client 连接探测 → 按平台出口配置准备请求中转 → 浏览器 → CDP 与目标页探测 → 人工操作入口探测 → 持久保存启动完成记录 → running。 - 不复用容器入口中的固定 `:99`,不删除不属于当前实例的 `/tmp/.X*-lock` 或 X11 socket。 - Xvfb 被杀、显示断开时停止本环境浏览器与操作入口,标记失败并保留 Profile;不得切换 headless,也不得自动重放业务发送。 - Xvfb 本身不是远程桌面。复用项目现有浏览器操作链路,提供该显示实例专属的 x11vnc/RFB 入口供人工登录、验证码和确认;只有画面可见而不能输入不算通过。不新增第二套远程桌面产品;需在实施第一阶段核对现有入口能否无损接入本机运行地址。 ### 4.3 本机进程归属与重启 每个运行实例使用独立的 systemd 用户 transient service/cgroup;由环境 runner 启动并监督本环境的 Xvfb、浏览器和 RFB 子进程,设 `KillMode=control-group`,禁止失败自动重启浏览器。主 gateway 重启与环境子服务分离,避免主服务退出就误杀仍需核验的环境。 创建 unit 前必须先持久保存启动归属记录,包含 alias、binding version、随机生成的 runtime ID、唯一 unit 名、Profile 路径、operation ID 与 `starting` 阶段;记录保存失败不得创建 unit。unit 必须携带可与记录核对的运行身份。随后将本次分配的 Xvfb 显示号、CDP 端点、中转监听地址与启动时间补入记录;所有就绪检查通过并持久保存启动完成记录后,才报告 running、允许业务动作和订阅。记录更新必须原子替换并确保落盘,不能以仅写入进程内存作为完成依据。 PID 仅用于诊断;重启时同时核对 unit 的运行身份、受控路径、运行记录和实际 CDP。Profile 独占锁在环境存活期间由环境 runner 持有,不因主 gateway 退出而释放;交接期间不得出现可被另一实例抢占的窗口。 重启恢复分支按以下顺序处理,恢复检查完成前不得报告 running 或接收业务动作: 1. 元数据、unit 或 Profile 所有权无法确认或不一致 → 进入 `unknown`,阻止同 Profile 再启动,不认领、不终止可疑进程;输出可定位原因,人工核验后显式回收。发现缺少记录的疑似托管 unit 也按此处理,不忽略残留。 2. 归属记录仍为 `starting`,尚未持久保存启动完成 → 不续跑启动、不自动重启;核对归属后,仅清理本次 unit 与临时资源,保留 Profile。即使浏览器已能连接,也不视为启动完成。确认资源已释放后记录 `failed`;清理结果无法确认则为 `unknown`。记录存在但 unit 尚未创建时同样收敛为失败,不创建新 unit。 3. 已完成启动的 unit 已退出 → 记录停止或失败,确认子进程已回收后释放锁与租约。 4. 已完成启动且归属相符的 unit 仍在运行 → 按第 5.4 节恢复请求中转,检查显示、CDP、所选出口及人工操作入口;人工入口须验证画面与输入能力,与首次启动标准一致。全部通过后才恢复运行报告,并在账号身份核验通过后恢复事件订阅,不新建浏览器、不重放业务动作。 5. 上述存活实例的任一就绪检查失败 → 不恢复业务与订阅,记录具体失败原因;归属已确认时停止本次运行并保留 Profile,清理完成为 `failed`,清理结果无法确认则为 `unknown`。不得以人工入口不可用、出口配置暂不可取等理由跳过检查。 采用 `systemctl --user` 管理现有用户服务,不引入 Python systemd 库。无用户服务管理器时启动配置失败,不回退到无人管理的后台进程。无图形登录的服务器重启后运行需由部署者配置用户服务驻留(linger);浏览器只依赖本环境 Xvfb,不要求已有桌面会话。 ## 5. 生命周期、数据与接口 ### 5.1 共享语义 | 动作 | 目标行为 | | --- | --- | | create | 保存已校验的环境配置并准备持久 Profile,返回停止态;不提前启动浏览器 | | start | 按 alias 串行;获取跨进程 Profile 独占锁;分配新 runtime ID;按第 4.3 节先保存归属记录,再准备显示、请求中转与浏览器并逐项验证。相同配置已就绪则返回当前实例,不再启动第二个 | | stop | 核对运行归属;先停接收新业务动作、停止订阅,再关闭浏览器,随后 RFB/Xvfb;保留 Profile | | recycle | 只回收本次受控运行资源与临时记录;保留环境、绑定和 Profile,供以后重新启动 | | upgrade | 停止旧实例并保留 Profile;使用已安装且启用的目标浏览器版本重新启动;不自动降级或回滚;失败不声称升级成功 | | rebind | 延续停止且无执行中任务的要求;更新出口绑定后,下次启动应用;不在运行中切代理 | `stopped → starting → running → stopping → stopped` 为正常路径;确认失败进入 `failed`,结果无法确认进入 `unknown`。这些是设计状态,实施时需在控制面、gateway 和页面一致表达,不把“进程存在”当成 running。对外动作结果不明时先查询核验,不自动重发业务写操作。 启动总时限初始定为 60 秒;停止给予浏览器 15 秒优雅退出,之后终止该 unit 的剩余进程树,总时限 30 秒。超时返回明确错误或 `unknown`,必须保留原始失败步骤和清理结果。强制退出需提示会话最后一次写入可能丢失。启动中途失败按创建顺序逆向清理,仅删除本次临时资源,绝不删除 Profile。 ### 5.2 Profile 与并发 Profile 固定绑定环境与宿主机:`/`,不同 alias 不共享目录。使用操作系统文件锁与 Chromium 自有锁共同防止重复启动;只检查目录存在或 Python 线程锁不够。两个 gateway 进程争抢同一 Profile 必须只有一个成功。 停止、回收、主 gateway 重启均不删除 Profile;升级也不搬迁 Profile。版本不兼容时明确失败,不自动清空登录数据。登录保存仅指保留平台允许持久保存的会话,不承诺平台不会撤销或要求重新登录。切换 gateway/宿主机视为新部署,禁止把跨机复制 Profile 包含在本轮。 ### 5.3 契约增量(待实现评审) 保持现有 `/api/browsers` 与 `/v1/browsers` 的领域入口及 Docker 动作含义;本机增加条件字段,不把本机浏览器伪装成 Docker 容器。以下是拟定契约,不是可直接调用的现有接口: | 位置 | 增量与约束 | | --- | --- | | gateway 能力查询(拟 `GET /v1/capabilities`) | 返回 `runtime_backend`、可用浏览器版本、人工操作能力;native 显示方式固定为 Xvfb;缺失或查询失败显示不可用,不猜测能力 | | gateway 注册信息 | 保存/展示后端能力;请求与当前能力不符拒绝;不依赖名称中是否带 docker 判断 | | 环境创建 | Docker 继续使用 `image_version`;native 使用 `browser_version`,不接受显示模式选择字段;两类版本字段互斥,错误组合返回 400 | | native 启动载荷 | 受校验 alias、binding version、浏览器版本、指纹与出口;不接收任意 executable、Profile 路径、DISPLAY、shell 或 CDP URL | | 运行记录/列表 | 明确后端、runtime ID、就绪状态与失败原因;native 不要求容器 ID 格式,也不伪造 `network_id` | | 代际校验 | 共享使用 alias、binding version、runtime ID;Docker 额外保留真实 network ID 校验;native 核对受控 unit 和进程归属 | | upgrade | 保留 `{version}` 动作形态,根据已确定后端查镜像版本或本机浏览器版本,不跨后端升级 | | 页面 | 按 gateway 能力展示对应版本;本机显示固定标注 Xvfb,不提供显示模式选择;错误、禁用原因可见,显式启停回收仍是独立动作 | HTTP 设计:输入错误 400;不存在的环境 404;运行归属、Profile 占用或版本冲突 409;显示/浏览器依赖不可用 503;下游执行失败 502;启动或清理超时 504。错误体保留现有可读错误信息并增加稳定错误码和 operation ID;不在错误体输出秘密。Docker 已有状态码不在本次顺带重写。具体字段、Go/Python 解析器、数据库约束和前端类型必须在同一实施变更中更新并测试。 开发阶段允许重建开发数据库,不做旧运行数据回填、双写或假字段兼容。已有 Docker 对外语义继续维护,不等于保留已经废弃的内部假设。`external_cdp` 不接入托管环境创建流程;其是否仍有独立诊断用途需检查调用方,废弃时直接删除,不成为启动失败的回退路径。 ### 5.4 代理、平台动作和监听 代理及其出口配置、凭据和环境绑定由平台管理;gateway 只按平台下发的配置中转请求,不创建、启停或自行选择上游代理。复用现有浏览器侧无需凭据的本地转发器;本机浏览器使用本机可达的转发地址,不能复用只在 Docker 网络内可达的 gateway 地址。出口凭据不进入浏览器命令行、配置文件或日志。明确直连与绑定代理是不同选择:绑定代理不可用必须失败,不能改为直连。 现有中转器运行于 gateway 进程内,主进程退出后不能假定中转仍可用。恢复时由平台依据 alias、binding version、runtime ID 核对当前运行及出口绑定,并重新下发所需中转配置和凭据;gateway 不从本地运行记录恢复秘密,也不自行更换出口。对仍存活的浏览器,须重建其原中转监听地址和端口,并通过实际浏览器请求确认所选出口可用后才能报告 running。平台无法提供配置、绑定不符、原端口被其他服务占用或出口验证失败时,执行第 4.3 节失败分支,不切直连、不换端口冒充恢复成功。重复下发同一运行配置应幂等,旧运行配置须拒绝;该恢复交互的具体接口纳入 P0 契约说明。 本机模式本轮不新增宿主机防火墙或网络隔离,不能宣称与现有 Docker 隔离等价。验收需证明实际浏览器请求经过选定代理、失败可见;不宣称已实现操作系统级防泄漏。 抖音/小红书继续使用现有页面内业务动作与身份校验;runtime 改变时旧订阅停止,新实例重新核验身份后建立订阅。人工发送仍逐次确认,自动响应仍由策略与 UID 冷却决定。平台通知保持事件监听;进程健康探测不是业务事件轮询的替代方案。断线期间平台若无法补齐事件必须显示缺口,不承诺无证据的无丢失恢复。 ## 6. 日志与失败可见性 关键节点均记录开始、完成或失败:配置校验、Profile 加锁、启动归属记录落盘、unit 创建、显示就绪、请求中转就绪、浏览器启动、CDP 就绪、人工入口就绪、启动完成记录落盘、恢复配置核对、中转重建、未完成启动清理、订阅恢复、停止、强杀与回收。 稳定字段:`service`、`operation_id`、`alias`、`runtime_backend`、`binding_version`、`runtime_id`、`stage`、`duration_ms`、`outcome`、`error_code`。进程退出补充 unit、退出码/信号;异常保留经过脱敏且有长度上限的 stderr。Profile 仅记录环境内相对路径;不输出 Cookie、密码、代理凭据、Xauthority 内容或完整 CDP WebSocket 秘密地址。 `/healthz` 只证明 gateway HTTP 进程存活;能力可用、环境 running、平台账号可用分别验证。失败清理也失败时,两条事实都要保留,不能只留下“启动失败”而隐藏残留进程。 ## 7. 分阶段实施计划 每阶段先写会失败的最小回归测试,再实现;前一阶段具备真实端到端证据后再扩展。开发前新建功能分支,本文提交不启动重构。 | 阶段 | 交付物 | 退出条件 | | --- | --- | --- | | P0 契约与能力核对 | 最小接口/schema 变更说明;锁定本机浏览器;证明 systemd 用户服务、Xvfb 分配、现有人工操作链路能使用 | 无假容器/网络字段;本机 Xvfb 方案实际可行;依赖来源与版本明确 | | P1 本机 Xvfb 最小闭环 | 一条从页面 create/start/stop/recycle 到 Xvfb 浏览器的链路;独立 Profile;人工操作入口;显示失败可见 | A01–A08、A10、A14 通过;Docker 最小回归通过 | | P2 多环境与故障处理 | 多个 Xvfb 实例并存;显示、端口冲突与进程故障清理 | A09、A11–A13 通过,无显示号或 Profile 冲突 | | P3 恢复与业务回归 | 重启调和(含未完成启动清理)、旧代际保护、请求中转恢复、平台读取与事件连接、完整日志 | A15 的重启会话保存及全部恢复分项通过;第 8 节全部必测用例与自动检查通过;真实平台结论与范围明确 | 优先复用 `gateway.py`、现有 DockerClient/代理/CDP 连接器;新增本机生命周期模块及测试。控制面、`internal/hub`、环境页面只增加实际需要的后端能力字段,不另建产品管理框架。实现时核对精确文件职责,不按本计划盲目拆文件。 ## 8. 验收方案 ### 8.1 场地和记录要求 准备一台没有图形登录/物理显示器的 Linux 主机验证本机 Xvfb;另保留现有 Docker 部署作回归。资源受限时,可用独立无显示会话的 Linux VM 验证,但不能用容器内已有 Xvfb 代替本机验收。无需准备真实桌面测试环境。 准备已锁定浏览器版本、专用测试 Profile、测试账号、可控代理、可发送测试事件的对端账号。真实发送仅用测试对象并遵守人工确认/自动策略。故障注入仅针对本次受控 unit,不使用 `pkill chrome` 等全局命令。 实施 PR 必须补齐可执行命令清单:安装依赖、创建用户服务、设置 gateway 配置、注册能力、调用生命周期接口、定位日志及故障注入。本文不提供尚未实现的虚假启动脚本。下面步骤通过现有页面动作描述,新增字段/操作入口待实现后必须可由测试人员直接执行。 每例记录:用例 ID、代码 commit、机器及依赖版本、运行后端、开始结束时间、输入摘要、operation/runtime ID、预期与实际、脱敏日志/截图路径、通过/失败/阻塞。没有证据一律不填“通过”。 ### 8.2 必测用例 | ID | 执行步骤 | 通过标准 | | --- | --- | --- | | A01 无 Docker 本机启动 | 在无 Docker daemon/socket 的主机配置 native,启动 gateway;查询健康与能力 | HTTP 可用,能力明确 native;不尝试连接 Docker;能继续创建环境 | | A02 参数校验 | 分别提交未知后端、版本不存在、非法显示模式字段、越界路径/非法 alias、混用两类版本字段 | 分别明确拒绝;没有新增 unit、浏览器或遗留临时文件;Profile 不被删除 | | A03 创建不启动 | 创建一个 native 环境;查看进程与页面状态 | 环境停止态;Profile 归属正确,无提前启动的浏览器/Xvfb | | A04 显示依赖校验 | 分别移除 Xvfb 可执行文件、使 X client 无法连接;再设置继承 DISPLAY 为其他显示服务地址并启动 | 依赖失败明确报错且清理本次资源,不切换 headless;正常启动只使用自建 Xvfb,不连接继承的显示地址 | | A05 停止/回收后会话保存(P1) | 人工登录测试账号,记录身份;依次 stop/start、recycle/start;gateway 重启恢复另见 A15(P3) | Profile 保留;平台未主动撤销时身份一致;若平台要求再登录则如实记录原因,不能用该情况掩盖 Profile 丢失 | | A06 无屏启动 | 无桌面主机清空继承 DISPLAY,启动 native 环境并使用人工入口访问、点击和输入 | 新建专属 Xvfb;浏览器 headed;远程画面和输入均正常;不需要物理屏幕 | | A07 人工登录 | Xvfb 人工入口完成平台登录,遇到验证码由人处理;读取自身身份 | 无自动注入密码/验证码;身份匹配;无法完成登录记阻塞,不以静态截图代替 | | A08 停止与回收 | 对本机 Xvfb 执行 stop 两次、start、recycle 两次;观察 unit/端口/显示/目录 | 动作幂等;受控进程树与监听端口已释放;Profile 保留;不关闭其他显示服务 | | A09 多环境 | 同一 native gateway 同时启动 Xvfb 环境 A、B、C;分别改变页面,再停止 B | 三个 Profile、runtime、显示号独立;B 停止不影响 A/C;Docker 环境同时正常 | | A10 同 Profile 争用 | 并发发送同 alias start;再让另一 gateway 实例争抢同一 Profile | 第一次最多一个浏览器;重复同请求返回已有实例或明确冲突;跨进程冲突 409;无 Profile 损坏 | | A11 显示与端口冲突 | 占用候选显示号;制造 CDP/RFB 端口占用;使 X client 连接失败 | 仅在正常分配阶段另选未占用资源或明确失败;不连接别人的服务;失败有阶段与清理证据 | | A12 子进程故障 | 分别终止受控 Xvfb、浏览器、人工操作服务;再次执行健康/状态查询 | 浏览器或显示失效不得仍报 running;必需操作入口失效可见;清理仅限本环境;没有自动重发动作 | | A13 启动/停止超时 | 让受控浏览器在启动阶段无响应、在停止阶段不退出 | 60 秒启动上限后失败并清理;停止 30 秒内终止受控进程树或标记 unknown;原始失败和清理结果可查 | | A14 不误杀 | 同用户另启非托管测试进程和独立 Xvfb 浏览器;启动/停止/回收托管环境;模拟记录 PID 被复用 | 非托管进程、独立显示服务与其他环境不受影响;所有权不符拒绝终止并报告原因 | | A15 gateway 重启与崩溃恢复(P3) | 按下方 A15 分项分别测试正常重启、运行中崩溃、启动中断、请求中转恢复、人工入口失效和归属异常 | 每个分项分别留证;匹配且就绪的已完成实例才恢复;未完成启动只清理,不新开浏览器;Profile 保留,残留资源和失败原因可查 | | A16 主机重启 | 保留 Profile,重启无图形登录主机,恢复用户服务并启动环境 | 不把旧 runtime/PID 当新实例;旧实例已退出;经显式 start 新建 Xvfb 运行实例,无需桌面会话 | | A17 代理与断线 | 本机 Xvfb 分别选直连、选测试代理;通过浏览器请求检查出口;再停代理 | 直连按选择工作;代理模式流量到达所选出口;失效可见且无自动直连;日志/参数无凭据 | | A18 旧代际请求 | 重启生成新 runtime;对新环境提交旧 stop/业务动作/订阅请求 | 409 或对应明确代际冲突;新实例未被操作,旧监听不继续发送 | | A19 版本升级 | 用已安装目标版本升级;再选不存在/不兼容版本;检查 Profile 和恢复操作 | 成功使用目标版本且保留 Profile;不存在版本在启动前拒绝;不兼容如实失败,无清空或降级 | | A20 平台链路 | 本机 Xvfb 先验证抖音自身身份和读取;由测试对端产生互动事件;按 plan01 验证确认/策略;随后同样核对小红书 | 真实事件来自当前账号/实例,无旧订阅重复执行;写前核对身份,人工逐次确认,自动遵守策略;平台缺口明确列为阻塞 | | A21 Docker 回归 | 原部署重复 create/start/stop/recycle/upgrade、出口故障、租约恢复和人工操作 | 原 Docker 行为不变;native 依赖不成为 Docker 启动条件;没有假 native 数据进入 Docker 解析 | | A22 页面与诊断 | 两类 gateway 切换创建环境,查看失败详情、禁用项、运行模式;按 operation ID 查完整启动失败链 | 只展示当前能力允许的字段;失败可定位;不展示秘密;服务健康与浏览器状态不混淆 | A15 必须分别记录以下结果,不得只验证运行中的直连环境: - **已完成实例与会话**:人工登录后分别正常重启、强制终止并重启主 gateway;原 runtime 不变、不新开浏览器,Profile 保留。平台未撤销会话时身份一致;平台要求重新登录时记录原因,不能掩盖 Profile 丢失。 - **启动中断**:分别在归属记录落盘前后、unit 创建前后、启动完成记录落盘前后终止 gateway。归属记录未落盘不得已有 unit;完成记录未落盘的本次资源仅清理、不续跑;完成记录已落盘的实例按完整就绪检查恢复。每次均核对进程、端口、显示、锁与 Profile,不能留下无法解释的残留,也不能重复启动。 - **归属与清理异常**:分别测试 unit 已退出、记录缺失或不符、Profile 所有权不符、清理失败;已退出如实停止,无法确认归属或清理结果时进入 unknown,阻止同 Profile 再启动,不误杀其他进程。 - **请求中转恢复**:使用平台绑定的测试代理,在浏览器运行时终止 gateway;重启后由平台重新下发匹配配置,验证原中转地址恢复且实际浏览器出口正确。另测配置不可取、绑定不符、原端口被占用、上游不可用;均不得恢复 running 或改为直连。重复下发不创建重复中转,旧运行配置明确拒绝。 - **人工入口失效**:保持 unit、显示、CDP 和出口可用,仅使 RFB 入口不可见或无法输入,再重启 gateway;不得恢复 running 或事件订阅,按失败分支清理并保留 Profile。 A20 的真实平台验收按 plan01 顺序执行:先抖音,后小红书。平台自身能力未验证不得阻挡对“本机运行层”作单独结论,但不得将运行层通过写成“两平台业务全部通过”。所有跳过项必须列出,不能默认视为通过。 ### 8.3 自动化检查与门槛 实现必须保留最小可复现回归测试:模式校验、条件载荷、Profile 跨进程锁、超时清理、归属与旧代际拒绝、重启恢复、秘密脱敏。用真实进程/文件锁验证生命周期关键边界;Mock 只用于可控错误注入,不代替真机显示验收。 - Python:`python -m unittest discover -s cmd`;覆盖率检查必须覆盖 native 生命周期与 gateway 集成,相关模块和整体单元测试覆盖率均不低于 65%。覆盖率工具加入开发依赖锁定文件,实施 PR 提供实际非交互命令及报告。 - Go:`go test ./...`、`go vet ./...`、`go build ./cmd/control-plane`;涉及运行状态/并发,必须加 `go test -race ./...`。 - 前端:`npm --prefix web ci`、仓库实际非交互测试命令、`npm --prefix web run build`;新增能力选择、失败/禁用状态有交互测试。 - Docker:`docker compose config --quiet`,构建、启动、健康检查及 A21;不得仅凭配置解析通过宣称运行通过。 - 本机:Xvfb 安装/启动命令、用户 unit 配置、故障注入步骤和 A01–A22 证据一并交付。 文档任务本身不需要运行上述功能测试;当前没有 native 实现,运行旧测试也不能证明它存在。 ## 9. 风险、排除项与交付判定 - **显示归属**:浏览器必须连接当前实例的 Xvfb,不能继承宿主机 DISPLAY 或误连其他显示服务;以连接探测与归属核验验证,不以进程存在代替。 - **浏览器发行版差异**:CDP、指纹参数和 Profile 升级行为需用所选二进制验证;不承诺任意浏览器兼容。Chrome 136 起针对默认数据目录的远程调试参数受到额外限制,专用 Profile 是必要设计,仍需验证所选 Chromium 派生版本。 - **同用户多显示会话**:Chromium 官方资料提示多图形 session 可能存在 D-Bus 串扰;不同 Profile 不足以证明多个 Xvfb 实例并发可用。P0 必须实测 D-Bus、密钥环与登录持久性;若需要独立 session bus,优先验证系统已有 `dbus-run-session`,证据不足时阻塞,不提前承诺隔离已完成。A09 除窗口互不影响外,还必须核对各自账号身份与重启后的持久数据。 - **远程人工操作**:Xvfb 能启动不代表登录可完成;A06/A07 是必测,不允许缩减为 headless 截图。 - **系统依赖**:systemd 用户服务与 Xvfb/x11vnc 为本机部署依赖;无此依赖不是“部分成功”,必须阻断并提示安装要求。 - **业务结果不确定**:gateway 崩溃后浏览器可能仍执行最后一次动作,恢复仅核验结果,不自动重放发送。 - **不新增**:Linux 真实桌面支持、Windows/macOS 支持、跨机器 Profile 迁移、任意 CDP/命令服务、自动登录、平台风控规避、认证系统或本机网络隔离。 功能验收结论必须分别填写:Docker 回归、本机 Xvfb、抖音业务、小红书业务。任一核心生命周期/归属/Profile 测试失败,本机 Xvfb 支持不能标为完成。真实账号或机器条件缺失时写“阻塞及所需条件”,不能写“预计通过”。 ## 10. 官方资料 下列为设计依据,不是本仓库已完成证明;引用运行约定,不复制实现代码。实际引入软件时登记所选版本、来源和许可证;Playwright 为 Apache-2.0,Chromium 使用 BSD 风格及第三方许可证,X.Org 组件以各包许可清单为准,systemd 以其发行包许可清单为准。 - Playwright CI / headed Linux 与 Xvfb: - Playwright persistent context / Profile 独占: - Chromium User Data Directory: - X.Org Xvfb 手册: - systemd transient service: - systemd 进程组终止规则: - systemd 服务停止与清理: - X.Org 显示连接检查与授权: - Debian `xvfb-run`(包含 `--wait` 已忽略的说明,不能照旧教程使用): - Chrome 远程调试参数变化: - Patchright Python 上游(Apache-2.0;headed 示例不等于锁定版本兼容保证):