Skip to content

♻️ 重构本地验证流程:常驻会话 + 命令驱动,替代一次性 spec - #1674

Open
CodFrm wants to merge 7 commits into
mainfrom
refactor/local-verification-session
Open

♻️ 重构本地验证流程:常驻会话 + 命令驱动,替代一次性 spec#1674
CodFrm wants to merge 7 commits into
mainfrom
refactor/local-verification-session

Conversation

@CodFrm

@CodFrm CodFrm commented Aug 13, 2026

Copy link
Copy Markdown
Member

Checklist / 检查清单

  • Fixes mentioned issues / 修复已提及的问题
  • Code reviewed by human / 代码通过人工检查
  • Changes tested / 已完成测试

N/A — 无关联 issue,本次是本地验证流程的自发重构。

Description / 描述

背景

本地验证一直是「写一个 spec → 整体跑一遍 → 看输出 → 想换个角度看 → 再改再跑」。
代价在仓库里是可量化的:e2e/scratch/ 里累计了 20 个一次性 spec、6871 行,
是 committed 套件(3251 行)的 2.1 倍,而它们几乎都只被跑过一次。

根因是没有可附着的浏览器:全仓 grep -rn "remote-debugging\|connectOverCDP" 零命中,
每次观察都必须从头启动一个 spec。

本次改动

  • e2e/session.mjs:启动一个常驻的、加载 dist/ext 的浏览器并保持存活。
    start / status / stop --all
  • e2e/drive.mjs:每次调用是一个短命进程,附着到会话做一件事。
    open / goto / click / fill / press / wait / snapshot / text /
    shot / eval / sw / storage / install / pages / use / close / console
  • 全上下文 console 采集:脚本自测与后台脚本的断言打在 Service Worker、
    Offscreen 文档、Sandbox iframe 里,它们各自是独立的 CDP target,Playwright 只暴露
    页面的 console 事件。改为连浏览器级 CDP 端点并递归 setAutoAttach,四类上下文
    统一落到一个 console.log 并标注来源。
  • E2E_HEADED:原先 9 处把 "--headless=new" 写死在 args 里,覆盖了
    Playwright 的 headless 选项 —— 也就是说 headless: false--debug 一直
    开不出窗口。现在统一走 headlessArgs()
  • 文档docs/verification.md 改以驱动会话为默认形式;e2e/README.md 新增
    §8 会话手册;驱动方法拆到 docs/references/verification-methods.md

实现考虑

  • 默认无头:验证不该抢占桌面焦点,--headed 只为人工旁观保留。
  • 并发:唯一全局争用的资源是 CDP 端口,因此向内核申请(listen(0))而不写死;
    profile 走 mkdtemp,证据落各自的 scenario 目录。多个 worktree 可同时验证。
    已实测两个会话互不串扰(一个装了脚本读到 1,另一个读到 0)。
  • 两阶段启动updateExtensionConfiguration 授权 userScripts 会重载扩展,
    重载期间扩展自己的页面返回 ERR_BLOCKED_BY_CLIENT。所以授权放在一次性启动里,
    再从同一 profile 重启接管 —— 与既有 fixture 已验证过的两阶段模式同源。
  • console 采集的三个坑(均已实测并修正):每个 target 只能 Runtime.enable
    一次,重复 enable 会重放该上下文的 console 历史造成成倍重复;不开
    setDiscoverTargets 就收不到 targetInfoChanged,页面导航后来源会一直停在
    附着那一刻的 about:blank;对象参数不带 value,必须按 preview 渲染,
    否则断言里的 {passed, failed} 只会记成 Object
  • 当前页按 URL 而非下标跟踪:扩展会自己开引导页,下标随时错位。
  • snapshot 的选择器优先级:testid > 稳定 id > 可见文本。Radix 生成的
    radix-_r_0_ 每次渲染都会变,明确排除。

已知限制

  • 采集器会在每个 target 上常驻一个 debugger 会话。这正是采集完整的原因,但也意味着
    会话不适合验证 Service Worker 的驱逐 / 空闲终止行为,那条线仍走
    keep-alive.spec.ts
  • 会话不提供协议 mock 与 .test 域名映射,需要 mock server 的验证仍走 scratch spec。
  • snapshot 不进 iframe。
  • e2e/scratch/ 下清掉了 21 个一次性文件;发版配图工具
    mobile-shots.spec.ts / promo-render.spec.ts / _assets/)保留,它们仍是平铺的,
    未纳入 <scenario>/ 约定。

建议审查重点

  • e2e/fixtures.tsheadlessArgs() 被另外三个 fixture 引入,确认 CI 仍为无头。
  • attachConsoleCollector()waitForDebuggerOnStart 要求每个 target 都必须收到
    runIfWaitingForDebugger,否则新 target 会被永久挂起 —— 这条路径值得重点看。
  • session.mjs 的 SIGTERM/context.on("close") 清理路径,是否会残留 profile 或进程。
  • drive.mjswrapEvalSource():带 return 当函数体、否则当表达式。

验证

pnpm run lint                                                                        exit 0
pnpm exec playwright test options.spec.ts script-management.spec.ts popup.spec.ts    12 passed
pnpm exec playwright test resource-update.spec.ts agent-navigation.spec.ts            6 passed
pnpm exec playwright test gm-api.spec.ts                                              9 passed
pnpm exec playwright test options.spec.ts popup.spec.ts gm-api.spec.ts               18 passed(采集器改动后复跑)

覆盖了全部 4 个被改动的 fixture 文件(fixtures / server-fixtures / agent-fixtures /
gm-api 内联 fixture)。

会话本身的实测(非推断):

  • 装脚本 → SW 确认 → options 列表出现 → 截图,全链路通过
  • 四类上下文的 console 均实测捕获:(src/service_worker.js)(src/sandbox.html)
    (装一个 @background 脚本并启用后打出的行)、(src/options.html);对象渲染为
    {passed: 29, failed: 0} 而非 Object
  • 两个会话并发且状态隔离;stop --all 后无残留 profile 与进程
  • E2E_HEADED=1session.mjs --headed 均确认开出真实窗口(UA 无 Headless
  • dist/ext/manifest.json 缺失时 start 拒绝启动,退出码 1
  • snapshotclick [data-testid="theme-toggle"]documentElement.className
    变为 darklightMode=dark

文档检查:6 个文档的 69 条相对链接与锚点全部解析通过;文中每条命令均已实跑。

CodFrm and others added 7 commits August 13, 2026 13:33
启动一次浏览器常驻,之后逐条命令附着驱动,验证一件事不再需要
先写一个 spec 再整体重跑。

- session.mjs:start/status/stop,默认无头,--headed 供人工旁观
- drive.mjs:open/goto/click/fill/snapshot/shot/eval/sw/storage/install 等
- 端口向内核申请(listen(0))、profile 走 mkdtemp、证据落各自的
  scenario 目录,多个 worktree 可并发验证
- 会话全程记录 console/pageerror 与操作,报告直接引用
原先 9 处把 "--headless=new" 写死在 args 里,而启动参数会覆盖
Playwright 的 headless 选项,所以 headless: false(含 --debug/PWDEBUG)
其实一直开不出窗口。统一走 headlessArgs(),E2E_HEADED=1 才可见。
verification.md 以驱动会话为默认形式,写 spec 收敛为「顺序/时序即契约」
时的例外;驱动方法拆到 references/verification-methods.md;e2e/README.md
新增 §8 会话手册(命令表、证据目录、并发、能力边界)。
Playwright 只暴露页面的 console 事件,但脚本自测与后台脚本的断言打在
Service Worker、Offscreen 文档和 Sandbox iframe 里 —— 这些各自是独立
的 CDP target。改为连浏览器级 CDP 端点并递归 setAutoAttach。

- waitForDebuggerOnStart 保证 document-start 的输出不漏
- 每个 target 只 Runtime.enable 一次:重复 enable 会重放 console 历史
- 开 setDiscoverTargets,否则导航后 URL 停在附着那一刻
- 对象参数按 preview 渲染,不再是无信息的 Object

Copy link
Copy Markdown
Collaborator

针对原 PR「♻️ 重构本地验证流程:常驻会话 + 命令驱动,替代一次性 spec」,我在原分支追加了 3 个修正 commits(未 force-push,未修改 PR 标题和描述):

  • c0dd9abd:为同一 scenario 增加原子启动锁;补齐启动失败、异常退出、正常停止和 stop --all 的 stale session/profile 清理;限制 scenario 路径边界,并避免停止超时后删除仍在使用的 profile。
  • 16523627:用 CDP targetId 追踪活动页面,修复重复 URL 页面在 use <i> 后可能操作错误 target 的问题;同时让页面管理和 console 命令写入 actions 记录。
  • 17aad4ce:将 E2E_HEADED 明确为 1/true/yes 才启用 headed,补充分支测试,并修正验证文档中未定义 tabId/url 的示例。

本地验证证据:

  • 新增的 9 条窄测试通过;
  • pnpm run lint 通过(含 typecheck、i18n、issue-template、ESLint 和 Prettier);
  • 相关 Playwright 回归 27/27 通过:options、script-management、popup、resource-update、agent-navigation、gm-api;
  • 常驻会话实测通过:drive 后会话仍存活、重复 URL target 选择正确、同 scenario 并发只保留一个会话、杀死 daemon 后 stop --all 清理 session/profile;
  • 发布后的分支 head:17aad4cec51f06c0128eaa04d00f8373c2886f8d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants