Commit a51bec9
✅ 抽离 example/tests 共用测试框架 (sctest) 并迁移 12 个测试脚本 (#1631)
* ✅ 新增 example/tests 共用测试框架内核与 Console reporter
* 🐛 修复 sctest toEqual 的 JSON 序列化误判并补测试
toEqual 原先用 stringify(actual) !== stringify(expected) 做深比较,导致
NaN 与 null 被误判相等、显式 undefined 键与缺失键被误判相等、对象键顺序
影响比较结果。改为手写的递归结构比较(Object.is 语义 + hasOwnProperty
探测键存在性 + 数组/对象类型互斥 + 循环引用防护),并补齐 toBeTruthy 的
真值/假值用例。
ConsoleReporter 的 MANUAL 分支此前丢弃了 c.hint,console-only 场景下
用户看不到人工确认需要做什么,现追加 hint 到既有 (待人工确认) 文案后。
* ✅ 测试框架新增 Shadow DOM 面板 reporter
* ✅ 测试框架新增 GM_log reporter 供后台/定时脚本使用
* 🐛 移除 emitLog 中吞异常的 try/catch,补全跳过/开始日志断言
typeof GM_log === "function" 的判断已完整覆盖未 @grant GM_log 的降级场景,
外层 try/catch 实际只是把已授权 GM_log 抛出的真实异常静默吞掉。移除
try/catch,让已授权 GM_log 的异常正常抛出。
同时为开始日志(sctest:"run")与跳过/人工用例日志(sctest:"case",
status:"skip")补上内容校验断言 —— 此前只有 key 数量断言,不会在
level/label 内容错误时失败。
* ✅ 测试框架补齐手动 suite 重跑通路与用法文档
* 🐛 修复面板失败详情在手动 suite 重跑时不渲染的问题
onCase 的更新分支此前只更新图标/耗时/统计,从不生成 .sc-detail,
而 auto:false 的 suite 每个用例首次真正执行时都会先被预渲染成 skip、
从而永远走这条分支——失败用例因此从不展示期望/实际/错误详情。
新增 renderDetail 统一由两条分支调用,重跑时先移除旧详情再按需重建,
避免重复追加;更新分支同时改用既有的 applyStatus 消掉重复表达式。
强化重跑用例的断言,校验行数不翻倍、跳过数清零,并补充详情展示与
失败转通过后详情清除的覆盖。
* ✅ e2e 支持把测试框架 @require 重写到本地 mock server
* ✅ gm_api_sync_test 迁移到共用测试框架
* 🐛 恢复 gm_api_sync_test 断言前的过程日志
GM_addValueChangeListener/GM_addElement 迁移时被误删的三条+两条日志,
均在断言之前/之间无条件执行,超时或挂起时仍会打印,是排查这两个
用例卡住位置的唯一线索,应予保留。
* ✅ gm_api_async_test 迁移到共用测试框架
* ✅ inject_content 与 early_inject 系列迁移到共用测试框架
迁移前后计数(全部一致):
- inject_content_test.js: e2e passed=11 failed=0 → passed=11 failed=0
- early_inject_content_test.js: 总计 14 通过 14 失败 0 → 总测试数 14 通过 14 失败 0
- early_inject_page_test.js: 总计 14 通过 14 失败 0 → 总测试数 14 通过 14 失败 0
后两个文件无 e2e 覆盖,用一次性 Playwright scratch 脚本核对。
两个 early_inject 文件显式指定 reporter: "console":它们断言 document-start 时
DOM 保持原始态,而面板会往 document.documentElement 挂 #sctest-panel-host,
正好破坏 expect(firstElement.innerHTML).toBe("") 这条断言。这两个文件因此
不显示页面面板——注入型 DOM 面板与 DOM 纯净断言无法共存。
* ✅ sandbox_test 迁移到共用测试框架
e2e gate (e2e/gm-api.spec.ts -g "Sandbox Test"): passed=32, failed=0
before migration; passed=32, failed=0 after migration. N unchanged.
* ✅ window_message 与 unwrap 系列迁移到共用测试框架
e2e gate (e2e/gm-api.spec.ts -g "WindowMessage Transport Test"): passed=5, failed=0
before migration; passed=5, failed=0 after migration. N unchanged.
e2e gate (e2e/gm-api.spec.ts -g "Unwrap scriptlet tests"): passed=3, failed=0
before migration; passed=3, failed=0 after migration. N unchanged.
unwrap_test.js has no e2e coverage; verified with a throwaway Playwright scratch
script (e2e/scratch/verify-unwrap-test.spec.ts, git-ignored) that installs the
migrated script and confirms it injects and reports passed=3/failed=0 on
https://example.com/?test_unwrap_123, and does not inject at all (no panel, no
console output) on https://example.com/?test_unwrap_excluded per its @exclude.
* ✅ gm_xhr_redirect_test 迁移到共用框架并接入 e2e
首个 B 类文件迁移:删除手写面板 + assertEq,接入 SCTest 框架,首次获得
可解析的汇总行与 e2e 覆盖。tests 数组(basicTests + useFetch 变体)保持
数据驱动,映射为 it(),未手动展开。
e2e 新增用例需要:
- patchTargetMatchCode 正则备选组加 GM_XHR_REDIRECT_TEST_SC token
- patchGMApiTestCode 新增 HB 常量重写规则(该文件及未迁移的
gm_download_test.js/gm_xhr_test.js 都用 `const HB = "https://httpbun.com"`
拼 URL,走模板字符串后原有的字面量 URL 替换规则匹配不到)
- mock server 补 /redirect-to 路由(302 + Location)
- mock server /get 路由补上查询串回显(该文件断言 response.url 带 query)
用例数:迁移前(原手写面板,真实 httpbun.com,一次性 scratch 脚本量得)
passed=12 failed=0;迁移后(新 e2e,走 mock server)passed=12 failed=0,
完全对齐。
* ✅ gm_download_test 迁移到共用测试框架
纯机械改写:删除手写面板与 logLine/setCounts/setStatus/setQueue 及本地
assertEq/assertTrue,26 个用例按 manual 标志拆成「自动套件」与「手动用例」
两个 auto:false suite(沿用 runAuto 原本 filter((t) => !t.manual) 的区分),
prefix 从 suite params 读取。
assertEq(a, b) 是实际在前,转成 expect(a).toBe(b) 不换位。断言语义逐条保持
不变——包括 test 18「empty URL」这条迁移前就在失败的用例,本提交不碰它的
判定,只做形式转换。
e2e 接入放在后续提交:本提交后该文件对真实 httpbun 仍是 19 通过 / 2 失败,
与迁移前基线一致。
* 🐛 修正 gm_download empty URL 用例一开始就写错的断言
该用例断言空 url 必须触发 onerror 或抛异常,但 ScriptCat 从未如此表现:
src/app/service/content/gm_api/gm_xhr.ts:230 对 url 统一做
new URL(urlResolved, window.location.href),GM_download 在同一函数的 :265-269
分支复用这条解析,空串按 RFC 3986 解析为当前页地址,下载因此正常成功。
不是迁移引入的回归——迁移前打真实 httpbun.com 就是失败的,对 e2e mock server
与解析源码三处一致且确定。属于 docs/references/develop-testing.md 里
「一开始就写错的断言」这条例外,单独提交以便独立复核或回退。
若日后判定「空 url 应当被拒绝」是正确的产品行为,改的是实现,本用例随之
翻回原判定即可。
* ✅ gm_download_test 接入 e2e
@match token GM_DOWNLOAD_TEST_SC 加进 patchTargetMatchCode;两个 suite 都是
auto:false,页面加载不会自动跑,给 runTestScript 加 beforeCollect 钩子在
page.goto 之后点一次「GM_download 自动套件」的运行按钮,手动用例保持不跑。
* 🐛 修复手动 suite 跑完后不重新发出 onEnd 的问题
runManualSuites 只逐条调用 onCase,从不调用 onEnd。后果是对**全部 auto:false 的
文件**,ConsoleReporter 的三行汇总永远停在页面加载时打的 "通过: 0 / 失败: 0"
(那时这些用例都被预置为 skip),LogReporter 的汇总日志同样永不出现。
面板因为靠 onCase 实时累加,看起来正常,把这个缺陷掩盖了。
三行汇总是 e2e 的解析契约,所以这等于 B 类文件根本无法用标准路径接入 e2e。
Task 12 当时是在 e2e 侧绕过去的——加了个 runSuiteAndCollectFromPanel 去爬面板
Shadow DOM 读结果。现在根因修好,这段绕行代码一并删除,B 类文件回到与其余文件
相同的 console 汇总路径。
- sctest.js: runManualSuites 结束时 buildSummary + 广播 onEnd,并返回 summary
- gm-api.spec.ts: beforeCollect 简化为「只点按钮」,返回 void;
轮询改为「先等首次汇总 → 快照计数 → 点击 → 等下一组汇总」,
避免快照取早了被首次的 0/0 立即满足
验证:sctest 单测 45/45(新增 3 条覆盖 Console/Log/返回值三条契约);
gm-api.spec.ts 全部 9 个用例通过,计数与各自基线一致
(inject_content 11、sandbox 32、gm_api_sync 29、gm_xhr_redirect 12、
unwrap_e2e 3、window_message 5、gm_api_async 29、gm_download 21,failed 均为 0)
* ✅ 测试框架新增用例内主动跳过通道 SCTest.skip
迁移前 gm_download_test 的 runOne 特判错误消息的 "SKIP:" 前缀来区分跳过,
迁移到共用框架后这个通道没了,5 个手动用例超时或人工点 Skip 全部记为失败。
改用独立的 SkipSignal 类型而非消息前缀嗅探:前缀嗅探会把消息碰巧同名的
真实错误一并吞成跳过。STATUS.SKIP、summary.skipped、面板 sc-chip-skip 与
LogReporter 的 ○ 分支本来就在,这里补的是从用例体内产生 skip 的入口。
顺带修一个真实浏览器里复现的遮挡:verdict bar 原本 fixed 在右上角,与
右下角最高 80vh 的 sctest 面板重叠 2px,两者 z-index 同为最大值而面板挂载
更晚,Skip/Pass/Fail 按钮被吃掉点击。改到左上角。
* ✅ gm_xhr_test 迁移到共用框架并接入 e2e
* ✅ gm_menu_test 迁移到共用框架,契约检查升级为断言
8 处 GM_registerMenuCommand 返回值契约检查从软打印 console.log(x === y) 升级为真断言 expect(x).toBe(y),放进 it() 用例;scratch 自动核过全部通过(总20 通过11 失败0 跳过9)。菜单点击观察点转 itManual() 按注册顺序交错;保留三个调试开关与全部注册/注销调用及回调内 console.log 面包屑,仅删除 waitActions/myResolve/waitNext 等待机制。gm_value_test.js 按决定不在本次范围,保持原样。
* ✅ gm_xhr_cookie_test 迁移到共用测试框架
12 个 test() → 12 个 it(),归入 3 个 describe。assert(expected,actual) 是期望在前,
转成 expect(actual).toBe(expected) 逐条换位;assertTrue → toBeTruthy。领域 helper
assertCookieValues 保签名与 slice().sort() 集合语义不变,内部 assert 改写为
expect(JSON.stringify(actual)).toBe(JSON.stringify(expected))。
矩阵段的 9 个依赖用例:原文用 `if (matrixRequestPassed && lastCookieMap)` 门控
(请求失败则这 9 个 test 不注册)。声明式框架无法条件注册,故引入 matrixOk 标志 +
每个依赖用例开头 `if (!matrixOk) SCTest.skip(...)`,复刻「前置请求失败则跳过依赖用例
而非各自级联失败」的原语义。
对真实 mockhttp.org 各跑 2 次稳定:迁移前基线 12/12/0,迁移后 12/12/0(含 skip 守卫,
happy path 无跳过)。
* 📝 更新 verification 文档以匹配统一后的测试框架输出
example/tests 下 14 个脚本迁移到 sctest.js 后,in-page self-test 只剩一种汇总格式,
不再有原来三种方言。改写「self-test pattern」小节:统一为框架的三行汇总,说明
background/crontab 走 GM_log、人工用例走 itManual,并标注 gm_value_test.js 是刻意
不迁移的交互式演示(无可判定断言、不打印汇总)。正则示例保留,注释从「三种布局」改为
「框架汇总行」。
* 📝 记录 sctest deepEqual 对 Date/Map/Set 的已知限制
终审建议(Minor ①): deepEqual 把 Date/RegExp/Map/Set 当普通对象比较,两个不同 Date
会相等。当前迁移用例的 toEqual 均未触及这些类型,仅补注释说明契约边界,行为不变。
* 🐛 修复 SCTest 面板 CSP 注入与设计对齐
* 🐛 修复 SCTest 面板交互功能
* 🐛 固定 SCTest 的提交引用
* fix: 修正 sctest 异常判定与重跑详情清理
* 🐛 修正 GM_download 空 url 测试契约
* 🐛 include manual cases in skip filter
* 🐛 install E2E permission watcher before scripts
* 🐛 wait for permission confirmation navigation
* 📝 clarify E2E dependency scope
* 🐛 watch existing permission pages
* 🐛 avoid abort race in DNS error tests
* 🐛 make blocked download test local
* ♻️ 回退 gm_download_test / gm_menu_test 至迁移前
8/8 审查指出的两个行为回归仍未修复:
- gm_download 人工用例经 itManual 注册但从不执行动作(fn:null),
面板可点通过/失败造成假阳性;
- gm_menu 丢失了 waitActions 的时间屏障,后续用例会改写待检查的菜单状态。
按 PR 讨论的回退方案,恢复这两个脚本迁移前的自包含实现:
- 恢复 example/tests/gm_download_test.js / gm_menu_test.js(无 @require、自带
运行器与人工确认流程);
- 移除 e2e/gm-api.spec.ts 中依赖 sctest 面板/统一输出的 GM_download E2E 用例
及其 @match 重写条目;共享的 sctest 本地重写与 mock server 保留(gm_xhr /
gm_xhr_redirect 等仍在使用)。
* 📝 把 in-page self-test 一节归位到 verification-methods 并修正过期注释
合并 #1674 时只有 docs/verification.md 被 git 标成冲突,但 #1674 已把
in-page self-test 这一节拆去新建的 references/verification-methods.md,
拆出去的那份没跟着改,留下了本 PR 已推翻的描述:
- "the line varies by script" —— 12/15 脚本共用 sctest,输出一致;
- 代码块里的「总计: N | 通过: N」「Total/Passed」两种格式全树 grep 零命中;
- "matches all three layouts" —— 只剩一种;
- 缺 跳过 行、itManual、GM_log 后台通道。
docs/README.md 指明这一节归 verification-methods.md 所有,因此把内容
移过去替换过期段落(保留该文件「会话 + spec 两写」的体例),
verification.md 只留指向它的链接 —— 顺带补上 #1674 漏写的反向链接。
同时修正 gm-api.spec.ts 两处随本 PR 失效的注释:
- beforeCollect 的「B 类文件 / 真实下载副作用」——「B 类」全树无定义,
且 GM_download E2E 已在 a6bad63 回退,现存唯一消费者是 gm_xhr_test.js;
该套件 auto:false 的理由在脚本自身已写明,此处不复述。
- 「三行汇总里的最后一行」—— 汇总现为四行,且末行是「跳过:」。
验证:tsc/eslint/prettier 通过;vitest 78/78(sctest 66 + verification-tools 12)。
---------
Co-authored-by: cyfung1031 <44498510+cyfung1031@users.noreply.github.com>1 parent 09fac37 commit a51bec9
19 files changed
Lines changed: 4600 additions & 3619 deletions
File tree
- docs
- references
- e2e
- example/tests
- lib
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
| 22 | + | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
26 | | - | |
27 | | - | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
28 | 29 | | |
29 | 30 | | |
30 | | - | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
31 | 36 | | |
32 | 37 | | |
33 | 38 | | |
34 | 39 | | |
35 | 40 | | |
36 | | - | |
| 41 | + | |
37 | 42 | | |
38 | 43 | | |
39 | 44 | | |
| |||
52 | 57 | | |
53 | 58 | | |
54 | 59 | | |
55 | | - | |
| 60 | + | |
56 | 61 | | |
57 | 62 | | |
58 | 63 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
60 | | - | |
61 | | - | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
62 | 64 | | |
63 | 65 | | |
64 | 66 | | |
| |||
0 commit comments