mjolnir-qa 0.4.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1199 -28
- package/README.ar.md +667 -0
- package/README.bn.md +678 -0
- package/README.br.md +706 -0
- package/README.bs.md +690 -0
- package/README.da.md +698 -0
- package/README.de.md +711 -0
- package/README.es.md +709 -0
- package/README.fr.md +714 -0
- package/README.gr.md +705 -0
- package/README.he.md +664 -0
- package/README.it.md +709 -0
- package/README.ja.md +692 -0
- package/README.ko.md +681 -0
- package/README.md +425 -152
- package/README.no.md +696 -0
- package/README.pl.md +699 -0
- package/README.ru.md +701 -0
- package/README.th.md +672 -0
- package/README.tr.md +699 -0
- package/README.uk.md +693 -0
- package/README.vi.md +680 -0
- package/README.zh.md +652 -0
- package/README.zht.md +652 -0
- package/dist/cli.d.mts +469 -22
- package/dist/cli.mjs +9780 -5230
- package/package.json +25 -7
package/README.zh.md
ADDED
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="assets/readme/logo.png" alt="Mjölnir — Verification Trust Engine" width="800" />
|
|
4
|
+
|
|
5
|
+
### 你的测试在对你说谎。我们来证明它。
|
|
6
|
+
|
|
7
|
+
**面向 QA 的 Verification Trust Engine。** Mjölnir 审计测试套件与 CI
|
|
8
|
+
流水线,给出可信度评分,并精确指出信任在哪里断裂。
|
|
9
|
+
|
|
10
|
+
[](https://www.npmjs.com/package/mjolnir-qa)
|
|
11
|
+
[](https://github.com/Sergey-Bar/Mjolnir/actions/workflows/ci.yml)
|
|
12
|
+
[](LICENSE)
|
|
13
|
+
[](https://nodejs.org)
|
|
14
|
+
|
|
15
|
+
[English](README.md) | 简体中文 | [繁體中文](README.zht.md) | [한국어](README.ko.md) | [Deutsch](README.de.md) | [Español](README.es.md) | [Français](README.fr.md) | [Italiano](README.it.md) | [Dansk](README.da.md) | [日本語](README.ja.md) | [Polski](README.pl.md) | [Русский](README.ru.md) | [Norsk](README.no.md) | [Português (Brasil)](README.br.md) | [ไทย](README.th.md) | [Türkçe](README.tr.md) | [Українська](README.uk.md) | [বাংলা](README.bn.md) | [Ελληνικά](README.gr.md) | [Tiếng Việt](README.vi.md) | [עברית](README.he.md) | [العربية](README.ar.md) | [Bosanski](README.bs.md)
|
|
16
|
+
|
|
17
|
+
> 🤖 Machine-assisted translation. The [English README](README.md) is canonical. Last synced: 2026-09-04.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx mjolnir-qa@latest
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**你的测试值得信任吗?**
|
|
24
|
+
|
|
25
|
+
[看它如何工作](#-看它如何工作) ·
|
|
26
|
+
[快速上手](#-快速上手) ·
|
|
27
|
+
[它检查什么](#-mjölnir-检查什么) ·
|
|
28
|
+
[评分](#评分如何运作) ·
|
|
29
|
+
[CI](#-ci-集成) · [配置](#配置) ·
|
|
30
|
+
[文档](#-文档)
|
|
31
|
+
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 🎬 看它如何工作
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<img src="assets/readme/demo.svg" alt="Mjölnir 对演示仓库的完整 --verbose 报告:WORTHINESS 75/100 NEEDS WORK,按类目拆分的诊断,FIX THIS FIRST 清单,以及每一条发现附带的规则 ID 与行号——覆盖 CI、Playwright、测试卫生与 Python 规则" width="900" />
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
<sub>`npx mjolnir-qa ./examples/demo-repo --verbose` 的完整输出,由真实
|
|
43
|
+
reporter 渲染——无任何删减。通过 `npm run docs:demo` 重新生成;
|
|
44
|
+
[`tests/demo-asset-reproducibility.spec.ts`](tests/demo-asset-reproducibility.spec.ts)
|
|
45
|
+
会在产物与工具实际打印内容发生偏差时令 CI 失败。</sub>
|
|
46
|
+
|
|
47
|
+
**刚才发生了什么:**
|
|
48
|
+
|
|
49
|
+
1. Mjölnir 发现了 Playwright 规格文件、它的配置、CI 工作流和一个
|
|
50
|
+
Python 测试文件——四种语言/格式,一趟扫描。
|
|
51
|
+
2. 它找到了削弱对测试套件信任的证据——掩盖任务失败的
|
|
52
|
+
`continue-on-error`、吞掉退出码的 `|| true`、硬性 sleep、脆弱的
|
|
53
|
+
选择器、硬编码的 staging URL、`networkidle` 等待。
|
|
54
|
+
3. 它把每一项变成带规则 ID、位置和修复方案的切实发现——以及一个你
|
|
55
|
+
可以用来给 PR 设门的单一分数。
|
|
56
|
+
|
|
57
|
+
### 近看一条发现
|
|
58
|
+
|
|
59
|
+
对上面第一条发现运行 `mjolnir explain QA-CI-001`,你会得到:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
▚▞ QA-CI-001 — continue-on-error masks a failing verification gate
|
|
63
|
+
|
|
64
|
+
Severity: error
|
|
65
|
+
Confidence: high
|
|
66
|
+
Evidence: E2
|
|
67
|
+
Measured FP: not yet measured — this rule ships on assumption (see docs/FP-AUDIT.md)
|
|
68
|
+
|
|
69
|
+
WHAT WAS FOUND (real detector output, not a mockup)
|
|
70
|
+
Job `security-scan` runs a verification gate under `continue-on-error: true`.
|
|
71
|
+
|
|
72
|
+
WHY IT MATTERS
|
|
73
|
+
This job can fail every day and CI will still show green. The checkmark
|
|
74
|
+
on this workflow cannot be trusted.
|
|
75
|
+
|
|
76
|
+
HOW TO FIX
|
|
77
|
+
Remove continue-on-error, or scope it to individual non-blocking steps only.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
这就是价值的单位:不是风格上的吹毛求疵,而是你的 CI 声称某事通过了、
|
|
81
|
+
实则未通过的那个位置。
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## ⚡ 快速上手
|
|
86
|
+
|
|
87
|
+
对一个仓库运行它,获得完整报告与可信度评分:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npx mjolnir-qa@latest
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**在 CI 中,产品就是一条命令。** 它只扫描分支改动的内容,出现新问题时
|
|
94
|
+
以非零码退出:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npx mjolnir-qa@latest --scope changed
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
把它放进 PR 检查——`mjolnir ci install` 会写好工作流——就完成了。
|
|
101
|
+
其余一切都是可选的。
|
|
102
|
+
|
|
103
|
+
| 命令 | 作用 |
|
|
104
|
+
| ----------------------------------- | ------------------------------------------------- |
|
|
105
|
+
| `mjolnir` | 全仓扫描 + 可信度评分 |
|
|
106
|
+
| `mjolnir --scope changed` | 只看你分支引入的内容——CI 形态 |
|
|
107
|
+
| `mjolnir ci install` | 生成建议性的 PR 工作流 |
|
|
108
|
+
| `mjolnir explain QA-CI-001` | 是什么 / 为什么 / 如何修复 + 单条规则的实测 FP 率 |
|
|
109
|
+
| `mjolnir rules --unmeasured` | 列出按假设而非测量运行的规则 |
|
|
110
|
+
| `mjolnir --json` / `--format sarif` | 机器可读 / GitHub Code Scanning |
|
|
111
|
+
| `mjolnir --strict` | 同时运行隔离层(quarantine)规则(FP 风险更高) |
|
|
112
|
+
|
|
113
|
+
<details>
|
|
114
|
+
<summary><strong>当某个测试不稳定时</strong></summary>
|
|
115
|
+
|
|
116
|
+
| 命令 | 作用 |
|
|
117
|
+
| ----------------------------------- | ------------------------------------------------ |
|
|
118
|
+
| `mjolnir forensics ./test-results/` | 真实运行数据 → `TRUE-FLAKE` 判定,`FLAKY.md` |
|
|
119
|
+
| `mjolnir triage ./test-results/` | 根据执行历史提出隔离建议 |
|
|
120
|
+
| `mjolnir pw-report ./test-results/` | Playwright 运行摘要——重试 / 不稳定 / 最慢 |
|
|
121
|
+
| `mjolnir doctor:playwright` | 仅 Playwright 的深度扫描 + Selector Health Score |
|
|
122
|
+
|
|
123
|
+
</details>
|
|
124
|
+
|
|
125
|
+
<details>
|
|
126
|
+
<summary><strong>偶尔使用 / 报告类</strong></summary>
|
|
127
|
+
|
|
128
|
+
| 命令 | 作用 |
|
|
129
|
+
| ------------------------------- | ------------------------------------- |
|
|
130
|
+
| `mjolnir fix --dry-run` / `fix` | 带证据的安全自动修复 |
|
|
131
|
+
| `mjolnir baseline` / `diff` | 先给发现拍快照,之后只报告新增/恶化项 |
|
|
132
|
+
| `mjolnir impact --since <ref>` | 自某个先前的提交以来改变了什么 |
|
|
133
|
+
| `mjolnir debt` | 带成本模型的测试债登记簿 |
|
|
134
|
+
| `mjolnir handover` | 为新 QA 提供的套件上手地图 |
|
|
135
|
+
| `mjolnir stats` | 本地统计所见过修复的累计计数 |
|
|
136
|
+
| `mjolnir badge` | shields.io 端点 JSON + 代码片段 |
|
|
137
|
+
| `mjolnir rules --md` | 完整规则目录(JSON 或 Markdown) |
|
|
138
|
+
| `mjolnir doctor` | 对 Mjölnir 自身规则库的自审 |
|
|
139
|
+
| `mjolnir create-rule <ID>` | 脚手架生成新规则 + 固定样例 |
|
|
140
|
+
| `mjolnir --format mermaid` | 用于 PR 评论的测试架构图 |
|
|
141
|
+
|
|
142
|
+
</details>
|
|
143
|
+
|
|
144
|
+
如果你更偏好,可以全局安装而不是 `npx`:`npm i -g mjolnir-qa`。
|
|
145
|
+
要求 Node.js ≥ 22.18。支持 Windows、macOS 和 Linux。
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 👥 这为谁而做?
|
|
150
|
+
|
|
151
|
+
- **QA / SDET**——拥有 e2e 或集成测试套件,需要证据证明套件确实配得上
|
|
152
|
+
它产出的绿色对勾。
|
|
153
|
+
- **平台 / DevEx 团队**——负责 CI 完整性与发布门禁;他们关心
|
|
154
|
+
`continue-on-error` 绝不能悄悄把红色流水线涂成绿色。
|
|
155
|
+
- **OSS 维护者**——想要一个便宜、常开、可本地和 CI 运行且零网络调用的
|
|
156
|
+
验证门禁。
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 🔨 Mjölnir 检查什么
|
|
161
|
+
|
|
162
|
+
| | |
|
|
163
|
+
| --- | ------------------------------------------------------------------------------------------------------- |
|
|
164
|
+
| ⚖️ | **可信度评分**——一个数字、透明的扣分表、没有黑箱 |
|
|
165
|
+
| 🎭 | **Selector Health Score**——为你的 Playwright 定位器评级,而不只是通过率 |
|
|
166
|
+
| 🔬 | **运行时取证**——读取真实的 Playwright/JUnit 运行数据来捕捉 `TRUE-FLAKE`,而不只是静态猜测 |
|
|
167
|
+
| 🚨 | **CI 完整性规则**——抓出 `continue-on-error`、`\|\| true` 等假绿花招 |
|
|
168
|
+
| 🐍 | **全部四种 Playwright 绑定**——TypeScript、Python、Java、C#/.NET——外加 pytest、JUnit/TestNG 和 CI 工作流 |
|
|
169
|
+
| 🔒 | **本地优先**——扫描时零网络调用、零遥测、几秒内完成 |
|
|
170
|
+
|
|
171
|
+
### 规则
|
|
172
|
+
|
|
173
|
+
每条规则都带有必须触发(must-fire)**和**必须不触发(must-not-fire)的
|
|
174
|
+
固定样例。会触发自身负样例的规则不能发布——这就是假阳性防火墙。
|
|
175
|
+
|
|
176
|
+
<details>
|
|
177
|
+
<summary><strong>测试卫生</strong></summary>
|
|
178
|
+
|
|
179
|
+
| ID | 规则 | Severity |
|
|
180
|
+
| ----------- | ---------------------------------------------------- | -------- |
|
|
181
|
+
| QA-TEST-001 | 提交了聚焦测试(`.only`、`fit`) | error |
|
|
182
|
+
| QA-TEST-002 | 无正当理由跳过的测试 | error |
|
|
183
|
+
| QA-TEST-002 | 有记录理由的跳过测试 | warning |
|
|
184
|
+
| QA-TEST-003 | 无断言的测试 | error |
|
|
185
|
+
| QA-TEST-004 | 硬性 sleep(`waitForTimeout`、`sleep()`、`delay()`) | warning |
|
|
186
|
+
| QA-TEST-006 | 用重试掩盖不稳定 | warning |
|
|
187
|
+
| QA-TEST-010 | 空测试主体 | error |
|
|
188
|
+
|
|
189
|
+
</details>
|
|
190
|
+
|
|
191
|
+
<details>
|
|
192
|
+
<summary><strong>测试质量</strong></summary>
|
|
193
|
+
|
|
194
|
+
| ID | 规则 | Severity |
|
|
195
|
+
| ------------ | ------------------------ | -------- |
|
|
196
|
+
| QA-TQUAL-001 | 仅用 mock 验证 | info |
|
|
197
|
+
| QA-TQUAL-002 | 同义反复的断言 | error |
|
|
198
|
+
| QA-TQUAL-009 | 未 await 的 promise 断言 | error |
|
|
199
|
+
| QA-TQUAL-011 | 被注释掉的测试 | warning |
|
|
200
|
+
|
|
201
|
+
</details>
|
|
202
|
+
|
|
203
|
+
<details>
|
|
204
|
+
<summary><strong>Playwright 🎭</strong></summary>
|
|
205
|
+
|
|
206
|
+
| ID | 规则 | Severity |
|
|
207
|
+
| --------- | ------------------------------------- | -------- |
|
|
208
|
+
| QA-PW-002 | 未 await 的 locator 断言 | error |
|
|
209
|
+
| QA-PW-003 | 提交了 `page.pause()` / `test.only()` | error |
|
|
210
|
+
| QA-PW-004 | 脆弱的 CSS/XPath 选择器 | warning |
|
|
211
|
+
| QA-PW-005 | 在 `page.evaluate()` 中写业务逻辑 | info |
|
|
212
|
+
| QA-PW-114 | 旧式元素句柄(`page.$`) | info |
|
|
213
|
+
| QA-PW-118 | `networkidle` 等待(天生不稳定) | info |
|
|
214
|
+
| QA-PW-123 | 硬编码的环境 URL | warning |
|
|
215
|
+
|
|
216
|
+
</details>
|
|
217
|
+
|
|
218
|
+
<details>
|
|
219
|
+
<summary><strong>CI 完整性</strong></summary>
|
|
220
|
+
|
|
221
|
+
| ID | 规则 | Severity |
|
|
222
|
+
| --------- | -------------------------------------------------- | -------- |
|
|
223
|
+
| QA-CI-001 | `continue-on-error` 掩盖失败 | error |
|
|
224
|
+
| QA-CI-002 | `\|\| true` 吞掉退出码 | error |
|
|
225
|
+
| QA-CI-005 | 消费报告却从不生成报告 | error |
|
|
226
|
+
| QA-CI-007 | 包在测试外面的重试包装 | warning |
|
|
227
|
+
| QA-CI-008 | 永远成功的步骤掩盖失败 | error |
|
|
228
|
+
| QA-CI-009 | 测试退出码未被传递(`\|` 没有 pipefail、`;` 串联) | error |
|
|
229
|
+
| QA-CI-010 | 在必须拦截的地方跳过测试(skip-on-PR 守卫) | error |
|
|
230
|
+
|
|
231
|
+
</details>
|
|
232
|
+
|
|
233
|
+
<details>
|
|
234
|
+
<summary><strong>Python / pytest 🐍</strong></summary>
|
|
235
|
+
|
|
236
|
+
| ID | 规则 | Severity |
|
|
237
|
+
| --------- | ------------------------------------ | -------- |
|
|
238
|
+
| QA-PY-002 | 跳过的测试(`skip`、非严格 `xfail`) | warning |
|
|
239
|
+
| QA-PY-003 | 无断言的测试函数 | error |
|
|
240
|
+
| QA-PY-005 | 测试中的 `time.sleep()` | warning |
|
|
241
|
+
| QA-PY-006 | 空测试主体(`pass`) | info |
|
|
242
|
+
| QA-PY-010 | 未冻结的随机/时间依赖 | info |
|
|
243
|
+
| QA-PY-012 | 同义反复的断言 | error |
|
|
244
|
+
|
|
245
|
+
共 20 条 Python 规则(QA-PY-001…012 pytest 卫生 + QA-PY-101…108 Playwright-Python)。
|
|
246
|
+
|
|
247
|
+
</details>
|
|
248
|
+
|
|
249
|
+
<details>
|
|
250
|
+
<summary><strong>Java / JUnit · TestNG ☕</strong></summary>
|
|
251
|
+
|
|
252
|
+
| ID | 规则 | Severity |
|
|
253
|
+
| --------- | ---------------------------------------- | -------- |
|
|
254
|
+
| QA-JV-101 | 被禁用的测试(`@Disabled`) | warning |
|
|
255
|
+
| QA-JV-102 | 硬性 sleep(`Thread.sleep()`) | warning |
|
|
256
|
+
| QA-JV-103 | 无断言的测试方法 | error |
|
|
257
|
+
| QA-JV-105 | Playwright 硬性 sleep `waitForTimeout()` | warning |
|
|
258
|
+
| QA-JV-106 | 脆弱选择器取代 role 定位器 | warning |
|
|
259
|
+
| QA-JV-108 | 测试里硬编码的环境 URL | info |
|
|
260
|
+
| QA-JV-111 | 全覆盖 mock `page.route("**")` | info |
|
|
261
|
+
|
|
262
|
+
</details>
|
|
263
|
+
|
|
264
|
+
<details>
|
|
265
|
+
<summary><strong>C# / .NET — NUnit · xUnit · MSTest 🟣</strong></summary>
|
|
266
|
+
|
|
267
|
+
| ID | 规则 | Severity |
|
|
268
|
+
| --------- | ------------------------------------------- | -------- |
|
|
269
|
+
| QA-CS-101 | 跳过的测试(`[Ignore]`、`[Fact(Skip=)]`) | warning |
|
|
270
|
+
| QA-CS-102 | 硬性 sleep(`Thread.Sleep` / `Task.Delay`) | warning |
|
|
271
|
+
| QA-CS-103 | 无断言的测试方法 | error |
|
|
272
|
+
| QA-CS-105 | 硬性 sleep `WaitForTimeoutAsync()` | warning |
|
|
273
|
+
| QA-CS-106 | 脆弱选择器取代 role 定位器 | warning |
|
|
274
|
+
| QA-CS-108 | 测试里硬编码的环境 URL | info |
|
|
275
|
+
| QA-CS-111 | 全覆盖 mock `page.RouteAsync("**")` | info |
|
|
276
|
+
|
|
277
|
+
</details>
|
|
278
|
+
|
|
279
|
+
> 完整的实时目录——每条规则的层级、置信度、假阳性风险与自动修复可用性——
|
|
280
|
+
> 由注册表生成:
|
|
281
|
+
>
|
|
282
|
+
> ```bash
|
|
283
|
+
> mjolnir rules --md
|
|
284
|
+
> ```
|
|
285
|
+
>
|
|
286
|
+
> 每条规则的页面位于 [`docs/rules/`](docs/rules/)。
|
|
287
|
+
|
|
288
|
+
### 这些规则中有多少经过测量
|
|
289
|
+
|
|
290
|
+
**99 条规则中有 74 条携带在真实 OSS 代码上测得的假阳性率**(每条 ≥ 10 个
|
|
291
|
+
人工分类的发现;见 [docs/FP-AUDIT.md](docs/FP-AUDIT.md))。其余 19 条按
|
|
292
|
+
作者的估计发布。每次扫描的页脚都会告诉你,_触发过的_ 规则中有多少经过
|
|
293
|
+
测量;`mjolnir rules --unmeasured` 列出未测量的;每条规则的
|
|
294
|
+
`mjolnir explain` 页面都声明其状态。即使数字难看我们也照样公布——
|
|
295
|
+
QA-CS-103 的实测假阳性率是 95%,因此被隔离。把这个 78 扩大,是项目的
|
|
296
|
+
持续性工作。
|
|
297
|
+
|
|
298
|
+
### 规则层级与语言成熟度
|
|
299
|
+
|
|
300
|
+
每条规则都是 `core`、`extended` 或 `quarantine`,依据其**实测**假阳性率
|
|
301
|
+
分配:
|
|
302
|
+
|
|
303
|
+
| 层级 | 含义 | 默认扫描 | `--strict` |
|
|
304
|
+
| ------------ | ------------------------------ | :------: | :--------: |
|
|
305
|
+
| `core` | 实测 FP ≤ 10 % | ✅ | ✅ |
|
|
306
|
+
| `extended` | 实测 FP ≤ 30 % | ✅ | ✅ |
|
|
307
|
+
| `quarantine` | 高于 30%,或尚未测量(n < 10) | ❌ | ✅ |
|
|
308
|
+
|
|
309
|
+
| 语言 | 适配器 | 当前覆盖 |
|
|
310
|
+
| --------------- | ---------- | -------------------------------------------- |
|
|
311
|
+
| TypeScript / JS | 编译器 AST | 最广、测量最多——主要为 `core`/`extended` |
|
|
312
|
+
| Python / pytest | 正则层 | 广泛、经语料库审计——主要为 `core`/`extended` |
|
|
313
|
+
| Java | 正则层 | 较新——主要为 `extended`/`quarantine` |
|
|
314
|
+
| C# / .NET | 正则层 | 较新——主要为 `extended`/`quarantine` |
|
|
315
|
+
|
|
316
|
+
TypeScript 与 Python 拥有最广的实测覆盖。Java 与 C# 已发布、有文档,
|
|
317
|
+
但在真实的消费方套件(不是绑定库自己的测试)接受审计之前,不进入主打
|
|
318
|
+
数字。
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## 评分如何运作
|
|
323
|
+
|
|
324
|
+
<p align="center">
|
|
325
|
+
<img src="assets/readme/terminal-hero.svg" alt="Mjölnir 终端输出——WORTHINESS 75/100 NEEDS WORK,按类目拆分的诊断与 FIX THIS FIRST 清单" width="820" />
|
|
326
|
+
</p>
|
|
327
|
+
|
|
328
|
+
<sub>通过 `npm run docs:hero` 重新生成;
|
|
329
|
+
[`tests/hero-asset-reproducibility.spec.ts`](tests/hero-asset-reproducibility.spec.ts)
|
|
330
|
+
会在产物与 reporter 实际打印内容发生偏差时令 CI 失败。</sub>
|
|
331
|
+
|
|
332
|
+
分数是透明的:**error −8、warning −3、info −1**,然后按套件暴露度
|
|
333
|
+
(每条测试声明的扣分)归一化。按证据加权的扣分意味着弱信号代价更低。
|
|
334
|
+
终端显示的正是评分所用的同一批折后数字——没有黑箱。完整方法:
|
|
335
|
+
[docs/SCORING.md](docs/SCORING.md)。
|
|
336
|
+
|
|
337
|
+
**判定**
|
|
338
|
+
|
|
339
|
+
| Score | 判定 |
|
|
340
|
+
| ------- | ---------------- |
|
|
341
|
+
| ≥ 80 | ✓ **WORTHY** |
|
|
342
|
+
| 50 – 79 | ⚠ **NEEDS WORK** |
|
|
343
|
+
| < 50 | ✖ **UNWORTHY** |
|
|
344
|
+
|
|
345
|
+
**证据等级**——每条发现携带一个;它决定该发现在分数中的权重:
|
|
346
|
+
|
|
347
|
+
| 等级 | 含义 | 对分数的影响 | 示例 |
|
|
348
|
+
| ---- | ---------- | ------------ | ---------------------------------------- |
|
|
349
|
+
| E2 | 确定性缺陷 | 全额扣分 | 提交了 `.only`——结构上可证明 |
|
|
350
|
+
| E1 | 启发式模式 | 一半扣分 | 正则匹配到 `sleep()`——信号强烈,但非证明 |
|
|
351
|
+
| E0 | 观察 | 零(仅提示) | 只报告,从不为 CI 设门,也不扣分 |
|
|
352
|
+
|
|
353
|
+
大多数规则是 **E1**。标语「we prove it」指的就是这套系统:E2 发现是
|
|
354
|
+
结构性证明;E1 发现是位置恰当的警告,不是形式化证明。
|
|
355
|
+
|
|
356
|
+
空仓库的分数是 `null`,绝不是虚假的 100——见
|
|
357
|
+
[信任模型](#信任模型)。
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## 🎭 Selector Health Score
|
|
362
|
+
|
|
363
|
+
Playwright 套件的头号指标——你的定位器有多抗造:
|
|
364
|
+
|
|
365
|
+
```text
|
|
366
|
+
▚▞ SELECTOR HEALTH — e2e/checkout.spec.ts
|
|
367
|
+
|
|
368
|
+
[█████████████████░░░] 83 / 100
|
|
369
|
+
role/text: 2 · testid: 1 · css-chains: 1 ⚠ · xpath: 0
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
基于角色的定位器拿满。CSS 类链与 XPath 会拖垮分数——它们在任何 DOM
|
|
373
|
+
重构时都会断,却不会告诉你是哪个行为回归了。
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## 🔬 运行时证据
|
|
378
|
+
|
|
379
|
+
静态不稳定性检测只是猜测。Mjölnir 读取**真实执行数据**——任何 runner
|
|
380
|
+
产出的 Playwright JSON 报告和 JUnit XML:
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
mjolnir forensics ./test-results/
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
```text
|
|
387
|
+
▚▞ FLAKINESS LEADERBOARD
|
|
388
|
+
|
|
389
|
+
3 tests · 1 failed · 1 flaky · 1 retried
|
|
390
|
+
|
|
391
|
+
TRUE-FLAKE completes checkout with saved card (e2e/checkout.spec.ts)
|
|
392
|
+
████████████████████ 6.0s · 2 attempts
|
|
393
|
+
FAILING declines an expired card (e2e/checkout.spec.ts)
|
|
394
|
+
████░░░░░░░░░░░░░░░░ 1.1s · 1 attempt
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
只在第 ≥ 2 次尝试才通过的测试不是通过的测试——那是碰运气的测试。无论
|
|
398
|
+
最终的绿勾如何,它都会被标记为 `TRUE-FLAKE`。
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## ⚡ Mjölnir 不是又一个 linter
|
|
403
|
+
|
|
404
|
+
Linter 告诉你代码是否守规矩。Mjölnir 告诉你你的验证能不能被信任。
|
|
405
|
+
|
|
406
|
+
| | ESLint / SonarQube | 覆盖率工具 | 人工评审 | **Mjölnir** |
|
|
407
|
+
| --------------------------------------------------- | :----------------: | :--------: | :------: | :---------: |
|
|
408
|
+
| CI 工作流完整性(`continue-on-error`、`\|\| true`) | ❌ | ❌ | 罕见 | ✅ |
|
|
409
|
+
| 一个工具覆盖多语言(TS、Python、Java、C#) | ❌ | ❌ | ❌ | ✅ |
|
|
410
|
+
| 为 Playwright 定位器的韧性评级(Selector Health) | ❌ | ❌ | 罕见 | ✅ |
|
|
411
|
+
| 标出没有真实断言的测试 | ✅(插件)\* | ❌ | 偶尔 | ✅ |
|
|
412
|
+
| 抓出硬性 sleep(`waitForTimeout`、`time.sleep`) | ✅(插件)\* | ❌ | 偶尔 | ✅ |
|
|
413
|
+
| 几秒内运行、扫描时零网络调用 | ✅ | ✅ | — | ✅ |
|
|
414
|
+
|
|
415
|
+
\*`eslint-plugin-jest`(`expect-expect`)与
|
|
416
|
+
`eslint-plugin-playwright`(`expect-expect`、`no-wait-for-timeout`)
|
|
417
|
+
为其各自框架覆盖了这些。
|
|
418
|
+
|
|
419
|
+
**运行时分析**是与静态 lint 并列的独立类别:
|
|
420
|
+
|
|
421
|
+
| | Playwright retry reporter | Allure / ReportPortal | **Mjölnir forensics** |
|
|
422
|
+
| ---------------------------------------- | :-----------------------: | :-------------------: | :-------------------: |
|
|
423
|
+
| 读取真实运行数据以得出 `TRUE-FLAKE` 判定 | 部分\* | 部分(标签) | ✅ |
|
|
424
|
+
| 基于执行历史的不稳定分诊报告 | ❌ | ✅ | ✅ |
|
|
425
|
+
| 与静态可信度评分集成 | ❌ | ❌ | ✅ |
|
|
426
|
+
|
|
427
|
+
\*Playwright 内部跟踪重试,但不会产出带判定标签的独立不稳定报告。
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## 🤖 为什么不直接用 AI 代码评审?
|
|
432
|
+
|
|
433
|
+
问题不同、层面不同。AI 评审能在 diff 里发现可疑的测试改动;但它无法
|
|
434
|
+
证明整个验证系统值得信任——而且它只看到你展示给它的 diff。
|
|
435
|
+
|
|
436
|
+
| | AI 代码评审(Copilot 等) | **Mjölnir** |
|
|
437
|
+
| ----------------------------- | :-----------------------: | :-----------------------: |
|
|
438
|
+
| 每次扫描成本 | Token(随 diff 大小增长) | **零**(本地、已安装) |
|
|
439
|
+
| 看到整个套件 + 所有 CI 配置 | 只有你展示的 PR diff | **每次都是全部** |
|
|
440
|
+
| 确定性(相同输入 → 相同输出) | ❌(非确定性) | **✅** |
|
|
441
|
+
| 抓出沉睡数月的模式 | 只在其进入上下文时 | **✅**(扫描所有文件) |
|
|
442
|
+
| 跨运行记住发现 | ❌(会话间没有记忆) | **✅**(baseline + diff) |
|
|
443
|
+
| 无人触发也能运行 | 需要 PR 或提示词 | **✅**(CI 钩子,3 秒) |
|
|
444
|
+
|
|
445
|
+
**两者都用。** AI 能捕捉任何正则都找不到的细微差别、意图与设计缺陷。
|
|
446
|
+
Mjölnir 捕捉 AI 因其看起来「像是有意为之」而放过的结构模式——提交进
|
|
447
|
+
仓库的 `.only`、被吞掉的退出码、测试任务上的 `continue-on-error`。
|
|
448
|
+
这些不是需要推理的 bug;它们是需要扫描的事实。
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## 🤖 CI 集成
|
|
453
|
+
|
|
454
|
+
一条命令生成 PR 工作流——默认建议性,绝不阻塞:
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
mjolnir ci install
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
或者通过 SARIF 原生接入 GitHub Code Scanning:
|
|
461
|
+
|
|
462
|
+
```yaml
|
|
463
|
+
- run: npx mjolnir-qa@latest --format sarif > mjolnir.sarif
|
|
464
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
465
|
+
with:
|
|
466
|
+
sarif_file: mjolnir.sarif
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
SARIF 的编辑器与流水线配置:[docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md)。
|
|
470
|
+
|
|
471
|
+
### 变更范围的覆盖
|
|
472
|
+
|
|
473
|
+
`--scope changed` 把发现归因于你的分支相对 `main` 合并基新增的行。它
|
|
474
|
+
覆盖测试文件(`*.spec.*`、`*.test.*`),以及 diff 中的 GitHub 工作流
|
|
475
|
+
文件和 Playwright 配置。当合并基无法解析——浅克隆、detached HEAD、
|
|
476
|
+
非 git 目标、默认分支不同——它会诚实地降级:发现回退到整文件归因,
|
|
477
|
+
报告会说明这一点。用 `--base <ref>` 覆盖基线引用。
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## 配置
|
|
482
|
+
|
|
483
|
+
Mjölnir 是零配置的。仓库根目录下可选的 `mjolnir.config.json`(或
|
|
484
|
+
`.mjolnir.json`)可以微调严重级别、门禁与范围——它从不改变检测语义。
|
|
485
|
+
|
|
486
|
+
| 键 | 类型 | 作用 |
|
|
487
|
+
| ------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
|
|
488
|
+
| `exclude` | `string[]` | 额外的忽略 glob(gitignore 子集),叠加在内建默认之上 |
|
|
489
|
+
| `gate` | `"advisory" \| "error" \| "warning"` | 哪些严重级别以非零码退出(默认 `error`;`advisory` 绝不阻塞) |
|
|
490
|
+
| `severityOverrides` | `{ "<RULE-ID>": severity }` | 为你的仓库重新排列某条规则的发现 |
|
|
491
|
+
| `ignore` | `IgnoreEntry[]` | 压制发现——**`reason` 必填**;条目 90 天后过期(显式的 `expires` 日期,或未注明时以配置文件的最后修改时间为准) |
|
|
492
|
+
| `plugins` | `string[]` | 第三方规则包(见[信任模型](#信任模型)) |
|
|
493
|
+
|
|
494
|
+
```json
|
|
495
|
+
{
|
|
496
|
+
"gate": "error",
|
|
497
|
+
"exclude": ["legacy/**"],
|
|
498
|
+
"severityOverrides": { "QA-PW-118": "warning" },
|
|
499
|
+
"ignore": [
|
|
500
|
+
{
|
|
501
|
+
"ruleId": "QA-TEST-004",
|
|
502
|
+
"files": ["e2e/legacy-login.spec.ts"],
|
|
503
|
+
"reason": "Third-party widget needs a settle delay; tracked in JIRA-4821",
|
|
504
|
+
"expires": "2026-12-31"
|
|
505
|
+
}
|
|
506
|
+
]
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
- **`.mjolnirignore`**——用于路径排除的纯 gitignore 风格文件,与
|
|
511
|
+
`exclude` 同一语法。机器级的噪音用它;当清单应当随其余配置一起进入
|
|
512
|
+
版本控制时用 `exclude`。
|
|
513
|
+
- **CLI 覆盖**——`--strict`(包含隔离层规则)、`--width <cols>` 与
|
|
514
|
+
`--ascii` / `--no-ascii`(终端渲染)、`--tone blunt`(更生硬的措辞)、
|
|
515
|
+
`--max-duration <sec>`(限时部分扫描)。
|
|
516
|
+
- 规则压制与弃用生命周期:[docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md)。
|
|
517
|
+
|
|
518
|
+
`ignore` 条目也为独立命令 `mjolnir suppressions` 提供数据,该命令列出
|
|
519
|
+
当前被压制的项以及每条的过期时间。
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
## 📐 退出码与契约
|
|
524
|
+
|
|
525
|
+
冻结——可以放心在其上构建 CI 逻辑:
|
|
526
|
+
|
|
527
|
+
| 退出码 | 含义 |
|
|
528
|
+
| ------ | ---------------------------------------------- |
|
|
529
|
+
| `0` | 干净——没有达到或超过门禁的发现 |
|
|
530
|
+
| `1` | 存在达到或超过门禁的发现 |
|
|
531
|
+
| `2` | 部分扫描(时间预算用尽、文件不可读)——绝不阻塞 |
|
|
532
|
+
| `10` | 用法错误(错误的标志、缺少目标) |
|
|
533
|
+
| `20` | 内部错误 |
|
|
534
|
+
|
|
535
|
+
JSON/SARIF 报告为 `schemaVersion: 1`。规则 ID(`QA-<FAMILY>-NNN`)一经
|
|
536
|
+
发布即不可变,且绝不复用。
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
## 信任模型
|
|
541
|
+
|
|
542
|
+
- **本地优先**——扫描期间零网络调用。任何时候都是。零遥测。
|
|
543
|
+
- **不做虚假证明**——我们宁可说「未知」也不说「已验证」。空仓库得到
|
|
544
|
+
`score: null`,绝不是虚假的 100。
|
|
545
|
+
- **部分诚实**——如果分析被截断,输出会说明。绝不会在未完成时声称
|
|
546
|
+
「complete」。
|
|
547
|
+
- **假阳性防火墙**——检测在去除注释/字符串的代码视图上运行
|
|
548
|
+
(TypeScript 规则使用编译器 AST):出现在散文注释或文档示例字符串中
|
|
549
|
+
的模式是文档,不是发现。
|
|
550
|
+
- **测量,而非断言**——只有具有来自真实 OSS 代码的假阳性率的规则才
|
|
551
|
+
进入主打层级(见[这些规则中有多少经过测量](#这些规则中有多少经过测量));
|
|
552
|
+
扫描页脚和 `mjolnir rules --unmeasured` 会告诉你哪条是哪条。
|
|
553
|
+
- **插件信任**——插件是在 `"plugins"` 下声明的 npm 包。**没有沙箱**:
|
|
554
|
+
插件代码以完整 Node 特权运行,与 ESLint 或 Vitest 插件相同的信任
|
|
555
|
+
模型。核心规则 ID 前缀是保留的,插件若使用将被拒绝以防伪装。
|
|
556
|
+
- **工作区本地外部规则**(基于文件夹、零网络)——扫描目标旁的
|
|
557
|
+
`mjolnir-rules/` 目录可加载自定义规则:JSON 文件声明正则模式(不执行
|
|
558
|
+
代码),`.mjs`/`.js` 模块导出 `rules`(完整 Node 信任,同插件)。外部
|
|
559
|
+
规则携带与核心相同的信任元数据;它们绝不能进入核心层级(核心要求
|
|
560
|
+
来自语料库侧文件的实测 FP 率——声明的 `tier: "core"` 会被压到
|
|
561
|
+
`extended`),遵守层级上限,并做漂移检查:`mjolnir rules --md --external`
|
|
562
|
+
从加载的文件渲染目录(来源 `external`),矩阵生成器接受 `--external <root>`。
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## 🏗️ 架构
|
|
567
|
+
|
|
568
|
+
<details>
|
|
569
|
+
<summary>展开目录树</summary>
|
|
570
|
+
|
|
571
|
+
```
|
|
572
|
+
mjolnir/
|
|
573
|
+
├── src/
|
|
574
|
+
│ ├── engine/ # LanguageAdapter interface + rule runner
|
|
575
|
+
│ ├── adapters/ # typescript · python · java · csharp · github-actions
|
|
576
|
+
│ ├── rules/ # rules across 8 families + the measured-FP table
|
|
577
|
+
│ ├── playwright/ # Selector Health Score engine
|
|
578
|
+
│ ├── discovery/ # workspace, frameworks, ignore resolution
|
|
579
|
+
│ ├── scope/ # git merge-base changed-scope engine
|
|
580
|
+
│ ├── scorer/ # transparent deduction table + prioritization
|
|
581
|
+
│ ├── reporter/ # terminal · JSON · SARIF 2.1 · Mermaid
|
|
582
|
+
│ ├── forensics/ # run-data ingestion · flake verdicts · triage
|
|
583
|
+
│ ├── config/ # mjolnir.config.json + suppressions
|
|
584
|
+
│ ├── plugins/ # third-party rule loading (no sandbox)
|
|
585
|
+
│ └── commands/ # every subcommand
|
|
586
|
+
└── tests/
|
|
587
|
+
├── fixtures/ # must-fire / must-not-fire per rule
|
|
588
|
+
└── golden/ # frozen score regression locks
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
</details>
|
|
592
|
+
|
|
593
|
+
- **规则是纯函数**——`(SourceFileContext) → Finding[]`,无 I/O,无
|
|
594
|
+
全局状态。增加一个生态 = 一个适配器 + 它的规则。
|
|
595
|
+
- **TypeScript/Playwright 使用编译器 AST**(ts-morph)。Python、Java 与
|
|
596
|
+
C# 运行在共享的、屏蔽注释/字符串的正则层上。
|
|
597
|
+
- 面向 Java 与 C# 的 tree-sitter WASM AST 层已存在,是下一步的精度
|
|
598
|
+
提升——尚未接入同步扫描管线。
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
## 📚 文档
|
|
603
|
+
|
|
604
|
+
| 文档 | 内容 |
|
|
605
|
+
| ------------------------------------------------------ | --------------------------- |
|
|
606
|
+
| [docs/SCORING.md](docs/SCORING.md) | 分数归一化 + 证据加权 |
|
|
607
|
+
| [docs/FP-AUDIT.md](docs/FP-AUDIT.md) | 实测假阳性率 + 方法 |
|
|
608
|
+
| [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md) | 规则状态、压制、弃用 |
|
|
609
|
+
| [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md) | SARIF 输出 + 编辑器/CI 配置 |
|
|
610
|
+
| [docs/rules/](docs/rules/) | 生成的逐规则目录 |
|
|
611
|
+
| [CONTRIBUTING.md](CONTRIBUTING.md) | 开发环境 + 贡献流程 |
|
|
612
|
+
| [CHANGELOG.md](CHANGELOG.md) | 发布历史 |
|
|
613
|
+
| [SECURITY.md](SECURITY.md) | 漏洞报告 |
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
## 📈 状态
|
|
618
|
+
|
|
619
|
+
**v0.5.x · 公开测试。** JSON 模式与退出码是冻结的契约。TypeScript 与
|
|
620
|
+
Python 的实测覆盖最广;Java 与 C# 较新——请通过
|
|
621
|
+
[层级表](#规则层级与语言成熟度)阅读。
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
## 🤝 贡献
|
|
626
|
+
|
|
627
|
+
新规则是最容易迈出的第一步——一条命令即可脚手架出规则及其必须触发
|
|
628
|
+
**和**必须不触发的固定样例(生成的规则会故意在样例上失败,直到你实现
|
|
629
|
+
真正的检测——占位桩无法发布):
|
|
630
|
+
|
|
631
|
+
```bash
|
|
632
|
+
mjolnir create-rule QA-PW-140 --title "Screenshot without diff bound"
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
完整的开发环境、常设门禁命令以及防蔓延 / 样例防火墙法则都在
|
|
636
|
+
[CONTRIBUTING.md](CONTRIBUTING.md)。
|
|
637
|
+
|
|
638
|
+
---
|
|
639
|
+
|
|
640
|
+
<div align="center">
|
|
641
|
+
|
|
642
|
+
**别再发布你无法信任的测试了。**
|
|
643
|
+
|
|
644
|
+
```bash
|
|
645
|
+
npx mjolnir-qa@latest
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
**Star ⭐ · Watch 👀 · Contribute 🤝**
|
|
649
|
+
|
|
650
|
+
由 [Sergey Bar](https://www.linkedin.com/in/sergeybar/) 构建
|
|
651
|
+
|
|
652
|
+
</div>
|