关于 meticulous-increase-coverage
meticulous-increase-coverage 是帮助团队扩展已录制浏览器会话覆盖率的 Meticulous 技能。它指导基线比较、可达 UI 流程选择,并提出 `.meticulousignore` 排除项。
主要功能
- 01测量 JavaScript 覆盖率基线
- 02识别不可达代码与覆盖目标
- 03追踪候选文件对应的 UI 操作
- 04使用浏览器驱动录制会话
- 05比较覆盖率增益并提出忽略规则
适用场景
- 01查找录制会话未覆盖的用户流程
- 02优先选择可提升覆盖率的功能区域
- 03排除已确认浏览器不可达的代码
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ alwaysmeticulous/ skills --skill meticulous-increase-coverage安装后在 Claude Code 中输入/meticulous-increase-coverage确认已加载。
附带文件references/worked-example.md
详细介绍
提高 Meticulous 项目的覆盖率
如果本次对话中还没有运行过
meticulous-cli-update,请先运行(它也会处理身份验证和项目选择)。
你需要交付的内容
需要提供两项独立的交付成果,二者缺一不可,不能互相替代:
- 一次或多次来自新录制会话的测试运行,并且能证明覆盖率有所提升。“证明”是指与基线进行比较,指出覆盖率提高了哪些文件,以及提高了多少(步骤 7)。如果某个目标确实无法覆盖,请如实说明,不要为了充数而列入。
- **一项提议修改
.meticulousignore的 PR。**只添加你确信完全无法覆盖的路径,通常是整个目录。仅仅是一次尝试中没能覆盖到的内容,不应加入其中;应将其留在列表之外,并在 PR 描述中说明。
开始前:在 main 上操作,并确保工作区干净
以下每条命令都依赖 CLI 从本地检出内容中解析信息:js-coverage 默认使用当前 git HEAD,trigger-test-run 默认使用 HEAD 作为部署版本,并使用与 origin 默认分支的合并基准作为基线。在 main 且工作区干净时,这些版本会收敛到同一个提交,正合你所需——没有差异,仅运行 HEAD,并且步骤 7 的合并结果会被 API 接受。
离开 main 会导致难以排查的问题:除非每次运行都执行了_完全相同_的提交,否则合并覆盖率会被拒绝;而 PR 的合并提交会在基分支变动时重新计算,因此,针对已前进的基分支较早触发的运行将无法与新的运行合并。
因此,请切换到 main、拉取最新内容,并确保在步骤 1 前 git status 显示工作区干净。
然后确认确实有可用的测试运行:
meticulous agent test-run-for-commit
记下它输出的 ID——步骤 7 会在必要时使用它。如果它报告 "No test run found for commit …",请停止并向用户说明;没有它就无法建立基线。最常见的原因是 CLI 指向了错误的项目,因此建议用户通过 meticulous auth get-project 检查。meticulous auth set-project 仅适用于 OAuth 令牌;API 令牌绑定到单个项目(因此 set-project 会失败),而注入的凭据不会留下可供选择的本地令牌——遇到这些情况时,需要更换凭据,而不是使用 set-project。
步骤 1 — 基线覆盖率
获取总体数据。这只需调用一次,也是步骤 7 和步骤 8 用于比较的数值:
meticulous agent js-coverage --summary
不要输出整个仓库的逐文件覆盖率。此技能中的每个问题都是范围较窄的查询(一个目录,或先查覆盖率最低的文件),因此一页结果就足以回答。每条分页命令都会在 stderr 中报告返回了哪些行以及匹配的文件数量(files 1-50 of 8214; …)。请留意这条提示:总数通常就是答案。
如果它报告 "No test run found for commit …"(如果上面的检查通过了,就不应该出现这种情况),请停止并向用户说明。不要用其他提交的运行来建立基线:步骤 7 的合并要求新运行和基线运行执行的是同一个提交。
基线运行的会话往往尚未全部回放完成,这会导致覆盖率被低估——如果 js-coverage 提示了这一点,请运行 meticulous agent complete-base-run(默认会等待,直到没有更多任务可以安排;可能需要一段时间,请稍后检查或重新运行,不要以为命令卡住了),然后重新运行 js-coverage。不要指望 unexecutedSessionCount 总是达到 0——有些会话可能永久无法获取,而 js-coverage 会容忍少量此类情况,不会因此一直拒绝运行。
步骤 2 — 区分死代码与实际目标
你要找的是两类不同的内容,最好分开处理:
.meticulousignore候选项:整个目录中的文件都统一处于 0%,这表明它们根本不会在浏览器中执行。- 覆盖率目标:真实用户流程可能会触达,但没有任何已记录会话恰好触达的文件。
两者各有对应的查询,全部基于 --orderBy。它会对本次运行中在服务端有可执行行的所有文件进行排名,也包括 0% 的文件(仅含类型的文件除外)(不需要 --includeAllFiles),并按路径打破并列。--globFilter 可将查询限定在某个目录,也可以重复使用。
忽略候选项
从这里开始,因为这能缩小列表范围。按照仓库本身的目录结构逐个检查目录(在 monorepo 中使用 ls packages/,在单个应用中使用顶层源代码目录),而不是根据覆盖率数据来选目录。对每个目录,查询其中执行次数最多的文件:
meticulous agent js-coverage --globFilter "packages/<name>/**" \
--orderBy=executedLines --includeLineCounts --limit=1
- **即使是该文件,
executedLines也为 0:**目录中的每个文件都处于 0%,标准错误提示会说明文件数量。这强烈表明该目录不会打包到浏览器中(后端、CLI 工具、文档、e2e 测试框架),因此它是.meticulousignore候选项。 - **没有匹配文件:**该目录已经不在覆盖率报告中,对它添加规则也不会产生作用。
- **其他任何情况:**该目录处于活跃状态。如果你怀疑其中有个别死角,请用同样的方法检查其子目录。
还有一个不依赖覆盖率数据的、更有力的信号。它能找出散落在其他活跃目录中的个别死文件,而逐目录检查完全无法发现这些文件。列出某个活跃目录中 0% 的文件(它们会按路径排在最前;当行的覆盖率高于 0% 时停止翻页):
meticulous agent js-coverage --globFilter "<live-dir>/**" \
--orderBy=coveragePercentage --order=asc --includeLineCounts --limit=100
对每个文件,在代码库中搜索它实际导出的符号,而不只是文件名:
# example only — adjust the source root and extensions to this repo
grep -rn "theActualExportedName" <src-root> --include="*.ts" --include="*.tsx"
如果唯一匹配项是文件自身的定义,则说明没有任何内容导入它,因此无论会话覆盖得多全面,都不可能执行它。这是不可达的证据,而不是根据没有记录作出的推断。即使本文件中已有目录级规则,也值得检查这一点:在其他部分仍然活跃的包中,死代码导出会不断累积。
有些类别在几乎所有情况下都可安全排除,如果逐目录检查确认其中的文件都统一处于 0%,就值得直接提出,无需进一步追踪。除了目录,这些类别也可以用 **/__tests__/** 或 **/*.stories.* 这样的 glob:
- 测试文件及其目录 —
__tests__/、__mocks__/、*.test.*、*.spec.* - Storybook —
*.stories.*、__stories__/、.storybook/ - 测试/模拟框架目录 —
testing/、mocks/、fixtures - 仅由 Node 执行的构建、lint 和代码生成配置 —
*.config.*、setupTests.*、脚本目录
其中一些内容可能已经完全不在覆盖率报告中。没有匹配任何文件的 glob 无需添加规则。
如果你知道某个目录会打包到前端(例如主应用会导入的共享组件/工具库),却发现其中所有内容都显示为 0%,就要保持警惕。这种情况更可能是 source map/路径归属问题,而非代码真的已死——请勿将其加入忽略列表,并将其标记为尚未解决。
覆盖率目标
这些文件不只有覆盖率为 0% 的文件。覆盖率为 12% 或 40% 的文件通常意味着其中一条路径已运行,其余路径尚未运行。这些部分覆盖的文件往往是最容易取得成效的目标:模块已经加载,只需再进行一次交互,就可能覆盖一大块代码。优先处理最有提升空间的文件,并将范围限定在应用的实际源代码中,这样测试、故事和待忽略候选文件就不会混进来:
meticulous agent js-coverage --globFilter "<app-src>/**" \
--orderBy=uncoveredLines --includeLineCounts --includeCoveragePercentage \
--limit=50
这会同时列出覆盖率为 0% 和部分覆盖的文件,百分比列可以区分它们。在提升空间相近时,优先选择部分覆盖的文件,而不是覆盖率为 0% 的文件。若要查看覆盖率最低的部分覆盖文件,请使用 --orderBy=coveragePercentage --order=asc。没有可排除覆盖率为 0% 文件的筛选条件,因此列表会从整组覆盖率为 0% 的文件开始;可使用 --offset 跳过这些文件。
按功能区域对候选文件分组,而不是只挑出覆盖率最低的单个文件。一次录制的流程通常就能让一组相关文件的覆盖率同时上升,因此,一个目录中十几个文件的覆盖率都在 5-20%,比一个藏在冷门分支后的孤立 0% 文件更值得优先处理。将查询范围限定到某个功能目录,即可查看这组文件。
有些覆盖率为 0% 或较低的文件,可能是因为默认关闭的功能开关而无法触达,而不是因为没有人走过相应的 UI 路径。在假设需要全新流程之前,请先检查功能开关注册表和文件的开关条件:在本地启用开关,就可能让一个看似无法触达的文件变成容易覆盖的目标。
第 3 步——追踪,不要猜测
对于每个候选文件,找到它实际的调用方,例如:
# example only — adjust the source root and extensions to this repo
grep -rln "<ExportedThing" <src-root> --include="*.tsx" | grep -v test
阅读调用方代码。确认:
- 可以通过简单、清晰的 UI 操作触达它(例如特定按钮或特定菜单项),而不是藏在功能开关后、被禁用的配置中(例如此环境中关闭的计费/SSO 开关),或只针对特定对象类型触发的条件分支中。
- 如果目标是一个钩子,请检查所有调用它的分支。钩子往往会被有条件地调用:一个分支可能会使用完全不同的机制(例如普通路由器
<Link>,而不是应用自己的导航钩子;打开侧边栏面板,而不是完整跳转)。请确认你的候选操作实际会走哪个分支。
第 4 步 — 驱动流程
以下三种驱动方式都能正确记录——已在两个不同的应用中验证:
- Claude in Chrome — 唯一能驱动用户自己已登录的 Chrome 配置文件的方式,因此已通过身份验证的应用无需登录流程。它的输入路径也是会卡住的那一种(见下文),所以要尽早验证,并做好切换准备。
- Playwright 和
agent-browser— 通过 CDP 启动的浏览器,速度更快,也更便于编写脚本,并且在这里的所有测试中都很可靠。代价是每次都会使用新的配置文件,因此必须登录。此外,agent-browser在目标元素被其他元素遮挡时会拒绝点击,并指出遮挡它的节点;这能捕获一类无声的误点击,而另外两种方式都会照常执行。
无论选择哪种方式,都要设置一个符合实际的视口。CDP 浏览器的默认视口较小(宽度约为 1280px),而实际的 Chrome 窗口通常是它的两倍。响应式布局会在不同宽度下渲染不同组件,因此默认视口可能会悄然覆盖不同的代码,或者隐藏你想要操作的控件。
相比工具的选择,下面这条规则重要得多。
只有可信事件才会被记录。 记录器会忽略在页面 JS 中合成的事件,因此 element.click()、设置 input.value 或派发自定义事件,都能让应用看起来正常运行,却不会记录任何内容。页面看起来一切正常,但会话记录却是空的。所有操作都要通过工具的真实输入操作来执行。
如果操作没有生效,请先查明是哪一部分出了问题,再改变策略——安装一个捕获探针,然后重复该操作:
window.__ev = [];
["pointerdown", "click", "keydown", "change"].forEach((t) =>
window.addEventListener(
t,
(e) => window.__ev.push({ t, trusted: e.isTrusted }),
true,
),
);
- 没有捕获到任何内容 → 你的输入从未到达页面;更换选择器或坐标也无济于事(见下文扩展程序卡住的说明)
- 捕获到了,但
trusted: false→ 输入已到达页面,但不会被记录;说明你在某处合成了事件
一次操作被记录为空的三种情况
这三种情况在浏览器中看起来都像是成功了,因此代价高昂——你要等到事后查看覆盖率数据才会发现问题。
原生 <select>。 通过表单填充操作设置值,或在 JS 中赋值,都会触发 isTrusted: false 的 change 事件——应用会作出响应,值也会明显更新,因此看起来像是成功了,但记录器会忽略该事件,回放时也就不会执行排序或筛选。点击 <select> 也不行:这会打开驱动程序无法看到的操作系统级弹出窗口。有效的方法是聚焦元素,然后按下选项可见文本的首字母("p" → “Priority”),这样会触发可信的 keydown 和可信的 change。如果多个选项首字母相同,可以重复按该字母来循环切换。可通过读取 e.isTrusted 的 change 监听器进行验证——无论哪种情况,值都会更新,因此仅凭值无法判断。
修饰键快捷键。 只有在录制时记录了一个_独立的_修饰键按下事件,且回放时该修饰键仍处于按下状态,回放才会还原修饰键。有些驱动程序只会发送基本键的按下事件:Claude in Chrome 的 cmd+k 和 agent-browser 的 press Alt+ArrowRight 都只会记录一个带有修饰键标志的按键事件,而不会单独记录修饰键按下,因此回放时标志会被清除,处理程序主体也就不会运行——但实时操作完全正常。Playwright 的 press('Alt+ArrowRight') 会记录独立的按键事件,回放也能正常工作(已通过覆盖率验证)。
因此:如果存在等效的_点击_目标,优先使用它;如果必须使用组合键,则通过 Playwright 操作,并在之后检查覆盖率。如果包含 if (… && event.metaKey) 的那一行已覆盖,但主体未覆盖,说明按键事件已传递,而条件判断结果为 false——这就是上述情况。
双击。 双击可能会被记录成单击,这种情况下回放不会触发 onDoubleClick,该会话中之后的每个事件都会指向从未打开的 UI,因此覆盖率会_下降_。请检查记录的事件数量,确认看起来有两组按下/释放事件;在覆盖率确认之前,将任何仅支持双击的功能都视为可疑。
有些交互可能根本无法通过 Agent 驱动的录制成功保留
有时,交互记录得很干净,实时操作时也明显有效,但它原本要触发的处理程序在回放时却从未运行——既没有报错,受影响的文件在步骤 7 的并集中的百分比也_恰好_保持在基线水平。已见过的一种情况是:在下拉菜单自身通过 portal 渲染的内容中输入值(例如筛选标签,或在“...”菜单后方重命名视图)。同一 portal 中的普通点击,以及在主页面树中的输入框上执行完全相同的“输入后按 Enter”操作,回放都正常——因此问题仅出现在 portal 中的键盘/文本输入,并非驱动程序故障。
不要仅仅因为实时交互成功,就假定输入值的目标已正常工作——请检查步骤 7 的差异。如果某个目标只能通过这种交互方式复现,且覆盖率无法提升,那就是此流程中 Agent 驱动录制的不足:请将其报告为未解决,并建议由人工手动操作该流程,而不是继续用重试来凑数。
claude-in-chrome 的具体说明
- 截图会缩小(约为原来的 0.6 倍),因此截图中的像素并非 CSS 像素。可以直接读取截图上的点击坐标,也可以将
getBoundingClientRect()的中心点乘以screenshotWidth / window.innerWidth进行换算。每次调整大小后都要重新计算,并重新截图,不要重复使用先前页面的坐标——布局会发生偏移,过期坐标可能会落在错误的元素上,记录下你并不打算执行的交互。 - 点击前立即重新获取
ref,绝不要跨页面或标签页重复使用。不要猜测编号——read_page不会按顺序生成编号。ref和坐标同样可靠,选用方便的即可。这并非claude-in-chrome独有的问题——agent-browser的snapshot中的ref也会以同样的方式过期;重复使用过期的ref会悄无声息地点击当前占据该ref的元素,而不是你原本想点击的元素。它确实造成过一次失误:过期的ref点中了表格中的“Add New”入口,创建了一条空白记录。DOM 可能发生变化时,每次点击前都要立即获取新的快照;如果点击后页面或记录数意外发生变化,应视为ref点击错了,而不是无关的错误——继续操作前,先清理它创建的内容。 - 输入偶尔会卡住。 每次点击和按键都会报告成功,读取操作也正常,但页面收不到任何输入。
ref和坐标会同时失效,因此这绝不是选择器问题;打开新标签页、重新导航、等待或重试都无法解决。彻底重启 Chrome 有帮助,但并不能持久修复。首次交互后、驱动完整工作流前,先检查探测结果;如果输入没有生效,请改用 Playwright 或agent-browser,而不是更换选择器方式。在整个过程中,两者都一直可靠,包括 Claude in Chrome 失效的同一时刻和同一页面。
操作,然后验证
- 导航到目标 URL,并执行实际操作。
- 通过 DOM 确认操作已成功(例如
document.body.innerText.includes(...)),不要只看截图——工具提示出现也可能看起来像是操作成功了。确认你仍在预期的页面上:如果应用悄悄将你重定向到了登录页面,盲目点击坐标只会点到空白处,最终得到一个没有任何事件的会话。 - 关闭标签页。
然后,在触发任何操作之前检查录制结果是否正常。关闭标签页后等待约 10 秒,让会话完成,并检查是否捕获到了内容:
meticulous agent sessions --limit 10 --excludeSyntheticSessions \
--includeDurationSeconds --includeNumberUserEvents \
--includeNumberUrlsVisited --includeStartUrl --includeAbandonedReason
查看刚刚生成的行:
- 完全没有行——还没有上传任何内容;先多等一会儿,再判断录制是否失败
numberUserEvents为 0——录制器没有检测到用户输入。你的点击没有传递到页面,或者是模拟的、不受信任的输入;回到上面的输入传递探测步骤。重放此会话毫无意义。- 多次导航时,
numberUrlsVisited为 1——后续页面没有进入_此_会话。它们可能进入了自己的会话(没问题,也收集这些会话 ID),也可能被吞成无法重放的尾部(参见步骤 5 中的硬导航说明)。 durationSeconds超过 300——重放时,超过 5 分钟的内容会被静默截断(步骤 5)。将超出的部分重新录制为独立会话,不要指望它能保留下来。abandonedReason有值——录制器放弃了此会话(参见步骤 5 中的 10 分钟上限);重放它不值得。startUrl不是你操作的页面——你关心的导航属于另一个会话,并非你以为的那个。
只有关闭标签页后,会话才算完成。 标签页打开时,该行只反映已上传的分块,因此其中较低或为零的 numberUserEvents 意味着“尚未刷新”,而不是“录制失败”。一次针对此技能的会话测量,标签页打开时显示 0 个事件,关闭后则显示 12 个。过早判断差点导致一段完全正常的录制被从头重做。
事件会按较短的间隔(几秒)上传,但卸载时尚未刷新的内容只会暂存在 sessionStorage 中,并在_之后再次加载同一来源的页面时_重新发送——因此,会话尾部可能会延迟到下次访问时才上传。尽量通过导航离开页面,而不要强行关闭浏览器,也不要立即判断录制结果。
如果一次要录制多个目标?每个目标录制后都要运行此检查,不要只在最后检查一次。只在最后检查,就无法判断哪个操作丢失了会话,而且你得把所有操作都重新执行一遍,才能找出需要重做的那个。
步骤 5 — 会话时间预算与关闭规范
- 云端回放的会话时间上限为 5 分钟。 此后记录的所有内容都会从回放中静默截断——运行仍会成功,并报告准确无误,因此除非你主动检查,否则不会发现内容丢失(快照路由会提前停止;允许的事件数远少于会话中的点击次数)。不要凭感觉判断:
agent sessions --includeDurationSeconds会给出时长,超过 300 秒就意味着尾部内容会丢失。 - 交互节奏会消耗这项预算。 每轮查找、点击和验证往返会占用 10-60 秒的_记录会话时间_。在打开标签页前规划好整个流程,将操作批量安排,尽可能减少往返次数,并在操作间短暂等待;流程中途不要验证,改为之后检查录制内容,并尽量将时长控制在 4 分钟以内。多次短会话胜过一次长时间全面检查。
- 直接通过 URL 导航是前往每个目标页面的有效快捷方式,且回放正常——优先选择它,而不是缓慢的点击路径。 但硬导航会将录制内容拆分为多个会话,因此之后要收集所有生成的会话 ID,并将它们全部传给
--sessionIds(步骤 6)。通常这没问题,每段都能控制在回放上限内。陷阱在于导航太快:如果在上一页面之后一两秒就到达新页面,它会被追加到该会话的_尾部_,而不是另开会话;尾部内容经常无法回放——因此页面在你操作时显示正常,却没有产生任何覆盖。每个你确实关心的页面都应停留一段时间(约 10 秒)再继续,并在步骤 6 中确认它作为独立的startUrl出现。普通的<form>提交也要注意:如果没有绑定onSubmit处理程序,就会触发真实的浏览器重新加载,而不是 SPA 跳转;如果随后立即导航,也同样可能丢弃刚录制但尚未刷新的会话。 - **录制器限制:**标签页打开时间达到 10 分钟硬上限时,整个会话会被标记为“已放弃”;上传每隔 5 秒刷新一次——最后一次交互后等待约 6-8 秒再关闭标签页。
步骤 6 — 收集会话 ID 并触发测试运行
先列出实际录制的内容,按时间从新到旧排列:
meticulous agent sessions --limit 20 --excludeSyntheticSessions \
--includeDurationSeconds --includeNumberUserEvents \
--includeNumberUrlsVisited --includeStartUrl
跳过 numberUserEvents 为 0 的行——它回放时什么也不会显示,只会稀释运行结果。注意 durationSeconds 超过 300 的行:它会回放,但只回放前五分钟,因此应将其尾部的覆盖视为缺失,而不要假设整个流程都运行了。
根据录制时间和 startUrl 识别你的会话。请注意,其他人以及指向同一项目的其他应用也会进行录制,因此不要想当然地认为最新的 N 行就是你的。列表较长时,--recordedSince 和 --visitedUrlFilter 是最快的筛选方法。
会话数可能多于你操作过的页面数:一次硬导航通常会结束一个会话并开启另一个会话,因此浏览五个页面可能会生成五个 ID。请全部收集。如果某个页面的 URL 从未作为 startUrl 出现,它很可能被并入了上一个会话的尾部,无法回放——如果需要覆盖该页面,请单独重新录制。
然后触发:
meticulous agent trigger-test-run --sessionIds "<id1>,<id2>,..."
步骤 7 — 使用并集进行比较,而非直接比较差异
先查看汇总结果。一次调用即可回答“是否有任何变化”,并且你可以直接将它与步骤 1 中的 --summary 比较:
meticulous agent js-coverage --summary --headPlusTestRunIds "<newRunId>"
这会将你的新运行并入从 HEAD 解析出的基准运行(也就是步骤 1 使用的同一个运行),因此结果包含基准覆盖范围 以及 新会话的覆盖范围。提交解析会跳过指定了显式会话集的运行,因此刚刚触发的运行不会被选为基准。你需要使用并集,因为该运行中的 --sessionIds 替换了 选定的会话集,而不是在其上追加。单独来看,你的运行覆盖范围远小于基准,直接与之比较会显示大量回归。并集只能增加覆盖范围,因此其数值绝不会低于步骤 1 的数值。
只传入你的新运行:运行不能与自身求并集。
然后查看你原本打算覆盖的文件。传入每个目标(重复使用 --globFilter),分别查询并集和不包含并集的基准:
meticulous agent js-coverage --headPlusTestRunIds "<newRunId>" \
--globFilter "<target1>" --globFilter "<target2>" \
--includeLineCounts --includeCoveragePercentage
meticulous agent js-coverage \
--globFilter "<target1>" --globFilter "<target2>" \
--includeLineCounts --includeCoveragePercentage
有两个迹象表明 HEAD 解析到的不是黄金集运行:并集因“是正在查询的运行”而被拒绝,或者其汇总结果_低于_步骤 1 的结果(你并入了另一个范围较窄的会话集;这并非回归)。最可能的原因是存在一个早于跳过逻辑的固定会话运行。无论是哪种情况,都要明确指定双方,使用步骤 0 中的基准 ID:
meticulous agent js-coverage --summary --testRunIds "<baselineRunId>,<newRunId>"
对每个目标进行查询时,使用相同的 --testRunIds 替代 --headPlusTestRunIds。
如果并集因运行对应不同提交而被拒绝,那就是 main/干净工作树这一前提条件在起作用——请参阅本技能开头。
步骤 8 — 验证并汇报
对于每个已追踪的目标文件:覆盖率是否有所提升?对于步骤 2 中每个 .meticulousignore 候选文件:它在并集中的覆盖率是否仍为 0%?重新运行步骤 2 的目录检查,并添加 --headPlusTestRunIds "<newRunId>"。如果 executedLines 仍为 0,说明你的会话没有触及该文件。
如果追踪充分的目标文件没有变化,不要以为是记录失败了——先重新检查会话(步骤 4:它是否存在、是否捕获了用户事件和 URL 访问、是否已放弃、其 startUrl 是否为你操作的页面),然后重新检查点击操作是否确实经过了你预期的文件(步骤 3),而不是同级或父级组件。若目标仍未解决,请如实汇报;没有覆盖率数据佐证时,不要宣称成功。
测试运行证明覆盖率是可达的,但除非将会话加入所选集合,否则后续定期运行不会有任何变化。将那些提升了目标文件覆盖率的会话提升为正式会话,不要等下次选择会话时再让系统挑选它们:
meticulous agent promote-sessions --testRunId="<newRunId>" \
--sessionIds="<id1>,<id2>"
只传入符合条件的会话——也就是步骤 7 显示覆盖率有所提升的会话——因为此后每次测试运行都会执行所有已提升的会话。只有在所有会话都符合条件时,才省略 --sessionIds。下次选择会话时,系统会将它们与集合中的其他会话重新排序,并剔除最终确认冗余的会话。
响应中的 updatedBaseTestRunId 是此次提交更新后的基础运行:包含基线和已提升会话的重放结果。后处理需要几分钟,在完成之前,此提交仍会解析到旧的基础运行——因此,提升会话后立即执行普通的 js-coverage 会返回旧数据,且不会提示它已过时。请改为按 ID 读取更新后的运行;该操作会等待运行完成:
meticulous agent js-coverage --summary --testRunId="<updatedBaseTestRunId>"
在报告中引用该数值:它是包含此次提升结果的提交自身数据,应与步骤 7 的并集一致。如果返回的 updatedBaseTestRunId 为空,stderr 中的通知会说明原因;此时请改为报告步骤 7 的并集。
只有当提交的基础运行已重放其全部所选会话时,才会允许提升;因此,如果你在步骤 1 中跳过了 complete-base-run,现在请运行它,然后再次进行提升。提升也有数量上限:每次调用最多 20 个会话,且两次会话选择之间最多可提升项目配置的所选集合大小的 10%。如果达到上限,请提升增益最大的会话,并在报告中列出其余会话。
步骤 9 — 提交 .meticulousignore PR
第二项交付内容。创建分支、提交 .meticulousignore 更改,然后发起 PR。
只提出你确信完全无法覆盖的路径。 标准是“没有任何会话能够执行这里的代码”,而不是“我今天没来得及覆盖”。优先使用目录级规则,而不是一长串单独文件的规则——添加新文件后,目录规则仍然有效,而文件列表会悄无声息地过时。实际上,大多数条目都来自步骤 2 中可安全排除的类别,以及步骤 2 的逐目录检查显示覆盖率始终为 0% 的整个非浏览器包。
提交前请确认:你准备忽略的每条路径在合并覆盖率中都保持在 0%(步骤 8 的检查)。你刚刚通过新会话覆盖的路径显然不应出现在忽略列表中,这项检查可以发现这类情况。
一种常见的结构,尤其是在单体仓库中,是“忽略所有内容,然后取消忽略浏览器中确实会运行的内容”:
# Ignore everything except packages that are executed in the
# browser and have meaningful frontend coverage.
packages/*
!packages/<frontend-app>/
!packages/<frontend-app>/**
!packages/<shared-ui-lib>/
!packages/<shared-ui-lib>/**
请注意,.meticulousignore 遵循 gitignore 语义,因此父目录一旦被排除,文件就无法重新纳入——除了取消忽略文件,还要取消忽略目录(!some/dir/**/)。
在 PR 描述中,说明每条规则的理由——_为什么_这些代码无法在浏览器中运行(它是仅限 Node 的构建脚本、测试框架,还是后端包)。基线只能告诉你某项内容今天的覆盖率为 0%,这绝不能证明它无法触达,因此理由必须基于代码的实际性质。另外,请明确说明:
- 尽管覆盖率较低,但你仍将其排除在忽略列表之外的内容,因为你怀疑这是源码映射/归属问题,而不是死代码(步骤 2)
- 仅因环境或功能标志配置而无法触达的内容,而非结构上无法触达的内容——这是由审阅者判断的事项,不是你的判断,因此应明确标出,而不是默默忽略
参考
references/worked-example.md 使用一个小型应用(kanban-demo)演示了完整的技能流程,并提供了实际覆盖率变化。值得一读,可供校准:其中没有任何 0% 覆盖率的文件,有一个目标的覆盖率有所提升,还有一个目标先成功记录、随后重放失败——文档还展示了如何根据执行范围区分两者。