| name | e2e-runner |
| description | Reproduce the real user path of the Baoyu Translate Chrome extension via chrome-devtools-mcp, run the three translation modes (quick / normal / refined) in isolation, and persist every artifact to tests/e2e-out/<iso8601-timestamp>_<git-sha>/<mode>/. Use when the user asks to run an e2e, debug a translation regression, refresh the ground-truth-style captured translations, or wants to verify the extension on a real page. Triggers on /e2e-runner, "run e2e", "end-to-end test the extension", "capture the live translation", or "refresh e2e-out". |
e2e-runner
复现真实用户路径的 e2e 流水线第一步。跑一次 = 三个翻译模式各跑一次,
per-mode 隔离(一个挂了不影响其他),所有产物落盘到
tests/e2e-out/<时间戳-git-sha>/<mode>/。
Slice 5 of PRD #26. 与 e2e-diagnostician / e2e-quality / e2e-reporter
串联,但本 skill 只负责复现用户路径 + 落盘,不做诊断 / 质量 / 报告。
加载顺序硬约束
install_extension → new_page 不可颠倒。
chrome-devtools-mcp 的 install_extension 不会把 content_scripts 注入到
已经打开 的 tab;反过来(先打开 URL 再 install)的页面 SW 端
chrome.tabs.sendMessage(tabId, { type: 'EXTRACT_CONTENT' }) 找不到 receiver,
popup 会永远报"无法识别页面正文内容"。详见 docs/testing.md 与 PRD #26 §Problem
Statement。
按以下顺序启动 e2e:
1. install_extension: path=$(pwd)/dist → 拿到 extension-id
2. list_extensions → 确认 "Baoyu Translate" Enabled
3. new_page: url=https://takes.jamesomalley.co.uk/p/this-might-be-oversharing
4. wait_for: text=This Might Be Oversharing → 确认页面 + content script 注入
5. take_snapshot → 拿 uid
如果顺序错了,立即停止并报告 "Loading-order violation"(含第一步实际执行
的工具名),不要尝试 reload_extension / evaluate_script 修复——这条路径
在 MCP 环境下不可恢复。
三模式 per-mode 隔离
跑一次 e2e 串行跑 quick → normal → refined。每次都重新生成译文,
不 cache 译文以供后续 e2e 跳过 LLM。理由:扩展代码
(prompt / protocols / injector)任何变更都可能影响输出,cache 会让回归
不可见(ADR-0003 §2)。
隔离原则:
- 一个模式挂了不影响其他:在
try { ... } catch 内跑单模式,失败写
failure.md 后继续跑下一模式。失败信息包括:错误消息 + a11y region 末
状态 + 过滤 [baoyu] 后的全量 console。
- 失败也写产物:
captured.md(失败说明占位)/ status.md(阶段末态
summary)/ console.jsonl(全量 console,过滤 [baoyu])/ failure.md
都写。e2e-report.md 体现该模式失败——翻译不成功也是一种结果,要在
报告里能看到。
- 顺序固定:
quick → normal → refined,LLM 配额预算按
PRD #26 §Further Notes(quick ≈ 1 次 chat,normal ≈ 2 次,refined ≈ 4 次)。
- 模式间不共享状态:每模式开始前
uninstall_extension + install_extension
是 overkill;正确做法是 reload_extension 清 SW 状态(task-orchestrator
内存里的 in-flight translation),然后用新的 trigger_extension_action
打开 popup。list_console_messages 在模式间不清空,多模式日志会混在
一起——落盘脚本按 phase/event 在 console.jsonl 里区分即可。
工具序列(per-mode)
每个翻译模式严格按以下序列执行。UID 来自 take_snapshot,不要猜测。
# 1) 打开 popup(已 install_extension 后)
trigger_extension_action: id=<extension-id>
# 2) 模拟真实 popup 尺寸
resize_page: width=320, height=500
# 3) 拿 popup 内按钮的 uid
take_snapshot
# 4) 点击目标模式按钮(quick / normal / refined)
click: uid=<popup-<mode>-button-uid>
# 5) 等待 a11y status region 反映到 a11y 树
# 文本来自 content-handler.ts::indicatorToStatusText():
# "translating 3/12" / "complete" / "error: <message>" / "cancelled"
# 字符串数组 = chrome-devtools-mcp any-match 数组(issue #1011)
wait_for: text=["complete", "error:"] # 失败信号也兜底
# 6) 抓注入译文集合([data-baoyu-element-id] 由 injector.ts 创建)
evaluate_script: |
Array.from(document.querySelectorAll('.baoyu-translation'))
.map(el => el.textContent.trim())
.join('\n\n')
# 7) 抓 console(只过滤 [baoyu] 前缀,由 ADR-0002 写入端定义)
list_console_messages
# 8) 抓 a11y region 末态
evaluate_script: |
document.getElementById('__baoyu_task_status__')?.textContent ?? ''
# 9) 落盘(见下"产物契约")
URL 固定
https://takes.jamesomalley.co.uk/p/this-might-be-oversharing
原文: tests/reference/article.md
Ground truth: tests/reference/article-zh-CN-{quick,normal,refined}/translation.md
URL 与 ground truth 不在本 skill 内部维护;如果上游变更需要同步更新 PRD #26
和 ADR-0003。
产物契约
每次 e2e 一个时间戳 + 短 git-sha 目录:
tests/e2e-out/
└── 2026-06-02T10-30-00_abcdef1/ # date -u +%Y-%m-%dT%H-%M-%S + git rev-parse --short HEAD
├── article-zh-CN-quick/
│ ├── captured.md # 注入译文集合(每段一行 trim 文本)
│ ├── status.md # url / mode / status / 段落数 / 批次 / duration / 末态 a11y
│ ├── console.jsonl # 过滤 [baoyu] 后的全量 console (JSONL)
│ └── failure.md? # 失败时存在:错误 + a11y 末态 + 全量 console
├── article-zh-CN-normal/
│ └── ...
├── article-zh-CN-refined/
│ └── ...
└── e2e-report.md # 本 runner 写:跑通/跑挂概览 + 产物索引
# 最终结构化报告由 e2e-reporter 覆盖
目录命名
TS=$(date -u +%Y-%m-%dT%H-%M-%S)
SHA=$(git rev-parse --short HEAD)
RUN_DIR="tests/e2e-out/${TS}_${SHA}"
mkdir -p "$RUN_DIR/article-zh-CN-{quick,normal,refined}"
captured.md shape
# article-zh-CN-quick
paragraph_count: <N>
captured_at: 2026-06-02T10:30:42Z
<译文 1 段>
<译文 2 段>
...
status.md shape
# article-zh-CN-quick
url: https://takes.jamesomalley.co.uk/p/this-might-be-oversharing
mode: quick
status: complete # complete | error | cancelled
paragraph_count: <N> # 注入译文段落数
batch_count: <B> # 来自 translate.batch 事件计数
duration_ms: <D> # task.start → task.complete 间隔
final_a11y_text: <text> # a11y region 末态("complete" / "error: ..." / "cancelled")
console.jsonl shape
每行一个 JSON 对象,来自 list_console_messages 过滤 [baoyu] 前缀后
JSON.parse 每条 console.log 的第二个参数。例:
{"phase":"extract","event":"start","ts":1717329000000}
{"phase":"extract","event":"done","ts":1717329000500,"data":{"paragraphCount":12,"durationMs":500}}
{"phase":"translate","event":"start","ts":1717329001000}
{"phase":"translate","event":"batch","ts":1717329002000,"data":{"current":1,"total":12}}
...
{"phase":"task","event":"complete","ts":1717329030000,"data":{"durationMs":29000,"paragraphCount":12}}
apiKey 字段已在 shared/logging.ts::redactPayload 阶段替换为 [REDACTED]
(ADR-0002 §Decision 1 / §Consequences 脱敏),落盘时无需再处理。
failure.md shape(仅失败时存在)
# article-zh-CN-<mode> — FAILED
error: <error message>
stack: <stack trace if any>
final_a11y_text: <a11y region 末态>
console_jsonl_lines: <N> # 过滤后行数
last_log_phase: <phase> # 最后一条 [baoyu] 日志的 phase
last_log_event: <event> # 最后一条 [baoyu] 日志的 event
last_log_ts: <ts> # 最后一条 [baoyu] 日志的时间戳
跨契约
ADR 契约(修改需同步两处):
[baoyu] 前缀 由 ADR-0002 写入端定义,本 skill 读取端复用同一前缀过滤
list_console_messages 输出。修改前缀需同步 ADR-0002 写入 + 本 skill 读取。
代码库契约(复用既有实现,不在本 skill 重新声明):
- 三模式枚举
quick / normal / refined 复用 shared/types.ts::TranslationMode。
- a11y region 文本 由
content-handler.ts::indicatorToStatusText() 决定:
translating <N>/<total> / complete / error: <message> / cancelled。
wait_for 仅匹配这些英文/数字形态,不匹配中文。
STATUS_REGION_ID = '__baoyu_task_status__' 由
src/content/task-status-region.ts 导出,本 skill 通过 evaluate_script
读它,不重新声明 ID。
测试范式
- 不用
setTimeout 等待——所有完成态判定都走 wait_for。
- 不用
take_screenshot 替代 take_snapshot 做元素定位(截图无 uid)。
- 不用
evaluate_script 直接调 SW / chrome API;唯一允许场景是
MCP 缺失能力(如读 a11y region 末态文本),需要在产物里显式说明绕开了哪一段。
- 不删除历史
tests/e2e-out/<ts-sha>/——时间序列累积。
- 不手改
dist/——MCP 操作的是 npm run build 产物;扩展代码变更后
必须 npm run build + reload_extension(PRD #26 §Testing Decisions)。
- 模式间不需
uninstall_extension/install_extension(隔离靠
reload_extension 清 SW 状态)。
不在本 skill 范围
- 诊断:失败原因 / 修复建议由
e2e-diagnostician 读本 skill 产物后产出。
- 质量对比:
captured.md vs tests/reference/.../translation.md 的
jaccard / 2-gram / Claude judge 评分由 e2e-quality 负责。
- 最终 e2e-report.md:本 skill 写一个"跑通/跑挂概览"占位,由
e2e-reporter 覆盖。
- CI artifact retention:本地产物由
.gitignore 屏蔽;CI 产物上传
retention 由 CI 系统决定,不在本 skill 范围。
用法
触发本 skill 后,按以下高层步骤执行:
-
Setup(一次):
cd 到 baoyu-translate Chrome extension 仓库根。
npm run build(确保 dist/ 是最新;MCP 操作 dist/ 产物)。
install_extension: path=$(pwd)/dist → 拿 extension-id。
new_page: url=<目标 URL> → 拿 target-page-id。
wait_for: text="This Might Be Oversharing" → 确认 content script 已注入
(看到 __baoyu_task_status__ 区域在 a11y 树里出现)。
-
Per-mode loop(quick → normal → refined):
mkdir -p tests/e2e-out/<ts-sha>/article-zh-CN-<mode>。
trigger_extension_action → resize_page: 320x500 → take_snapshot →
click: <mode>-button-uid。
wait_for: text=["complete", "error:"](长超时,建议 5 min)。
- 抓
evaluate_script(.baoyu-translation 集合)+ list_console_messages
(过滤 [baoyu]) + a11y region 末态。
- 落盘
captured.md / status.md / console.jsonl,失败则加 failure.md。
- 跑下一模式前
reload_extension 清 SW 状态(in-flight translation)。
-
Tear-down:
uninstall_extension: id=<extension-id>。
- 写
tests/e2e-out/<ts-sha>/e2e-report.md(占位:跑通/跑挂概览 + 产物
索引 + console.jsonl 总行数),由 e2e-reporter 覆盖。
-
告诉用户:"Runner 完成。产物在 tests/e2e-out/<ts-sha>/。下一步
调 /e2e-diagnostician 跑诊断 / gate。"
故障排查
| 现象 | 根因 | 修复 |
|---|
| popup 报"无法识别页面正文内容" | 加载顺序反了(先 new_page 再 install_extension) | 删 tab 重新 install_extension → new_page |
wait_for text=["complete"] 超时 | 网络/API key 失效,或 protocol 阶段崩了 | 抓 console.jsonl 看最后 phase/event + 末态 a11y |
take_snapshot 拿不到模式按钮 uid | popup 没 resize 到 320x500 / content 加载未完成 | resize_page + wait_for 后重试 |
| console.jsonl 是空 | 没看到 [baoyu] 前缀 → ADR-0002 日志未启用或 content script 未注入 | list_console_messages 不带过滤看 raw 输出 |
install_extension 失败 | Chrome for Testing 版本 < 149 | npx @puppeteer/browsers install chrome@stable |
参考
- PRD #26:父 PRD(5 切片之一)。
- ADR-0002:日志 schema +
[baoyu] 前缀契约。
- ADR-0003:e2e 流水线 + 产物契约 + 双层对比。
docs/testing.md:MCP 加载顺序约束 + 工具用法。
src/content/task-status-region.ts:a11y region 单一入口 setStatusText。
src/content/content-handler.ts::indicatorToStatusText():a11y region 文本
来源(slice 2 / issue #28)。
src/shared/logging.ts:日志 schema + 脱敏 + 开关(slice 3 / issue #29)。