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.da.md
ADDED
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="assets/readme/logo.png" alt="Mjölnir — Verification Trust Engine" width="800" />
|
|
4
|
+
|
|
5
|
+
### Dine tests lyver for dig. Vi beviser det.
|
|
6
|
+
|
|
7
|
+
**Verification Trust Engine til QA.** Mjölnir auditor testsuiter og
|
|
8
|
+
CI-pipelines, rapporterer en værdighedsscore og viser præcis, hvor
|
|
9
|
+
tilliden brister.
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/mjolnir-qa)
|
|
12
|
+
[](https://github.com/Sergey-Bar/Mjolnir/actions/workflows/ci.yml)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
[](https://nodejs.org)
|
|
15
|
+
|
|
16
|
+
[English](README.md) | [简体中文](README.zh.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.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)
|
|
17
|
+
|
|
18
|
+
> 🤖 Machine-assisted translation. The [English README](README.md) is canonical. Last synced: 2026-09-04.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx mjolnir-qa@latest
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Er dine tests værd at stole på?**
|
|
25
|
+
|
|
26
|
+
[Se det virke](#-se-det-virke) ·
|
|
27
|
+
[Hurtig start](#-hurtig-start) ·
|
|
28
|
+
[Hvad den tjekker](#-hvad-mjölnir-tjekker) ·
|
|
29
|
+
[Scoring](#sådan-fungerer-scoren) ·
|
|
30
|
+
[CI](#-ci-integration) · [Konfiguration](#konfiguration) ·
|
|
31
|
+
[Dokumentation](#-dokumentation)
|
|
32
|
+
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 🎬 Se det virke
|
|
38
|
+
|
|
39
|
+
<p align="center">
|
|
40
|
+
<img src="assets/readme/demo.svg" alt="Mjölnirs fulde --verbose-rapport over et demo-repo: WORTHINESS 75/100 NEEDS WORK, en opdeling af diagnostik efter kategori, en FIX THIS FIRST-liste og hvert fund med regel-ID og linjenummer på tværs af CI-, Playwright-, testhygiejne- og Python-regler" width="900" />
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<sub>Det komplette `npx mjolnir-qa ./examples/demo-repo --verbose`-output,
|
|
44
|
+
renderet af den rigtige reporter — intet klippet væk. Regenereres med
|
|
45
|
+
`npm run docs:demo`;
|
|
46
|
+
[`tests/demo-asset-reproducibility.spec.ts`](tests/demo-asset-reproducibility.spec.ts)
|
|
47
|
+
får CI til at fejle, hvis artefaktet afviger fra, hvad værktøjet
|
|
48
|
+
printer.</sub>
|
|
49
|
+
|
|
50
|
+
**Hvad der lige er sket:**
|
|
51
|
+
|
|
52
|
+
1. Mjölnir opdagede Playwright-specs, dens konfiguration,
|
|
53
|
+
CI-workflowet og en Python-testfil — fire sprog/formater, ét
|
|
54
|
+
gennemløb.
|
|
55
|
+
2. Den fandt beviser, der svækker tilliden til suiten — en
|
|
56
|
+
`continue-on-error`, der maskerer et job, en `|| true`, der synker
|
|
57
|
+
en exit-kode, hårde sleeps, en skrøbelig selector, hårdkodede
|
|
58
|
+
staging-URL'er, en `networkidle`-venten.
|
|
59
|
+
3. Den gjorde hver af dem til et konkret fund med regel-ID, placering
|
|
60
|
+
og fix — og til én score, du kan gate en PR på.
|
|
61
|
+
|
|
62
|
+
### Ét fund på nært hold
|
|
63
|
+
|
|
64
|
+
Kør `mjolnir explain QA-CI-001` på det første fund ovenfor, og du får:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
▚▞ QA-CI-001 — continue-on-error masks a failing verification gate
|
|
68
|
+
|
|
69
|
+
Severity: error
|
|
70
|
+
Confidence: high
|
|
71
|
+
Evidence: E2
|
|
72
|
+
Measured FP: not yet measured — this rule ships on assumption (see docs/FP-AUDIT.md)
|
|
73
|
+
|
|
74
|
+
WHAT WAS FOUND (real detector output, not a mockup)
|
|
75
|
+
Job `security-scan` runs a verification gate under `continue-on-error: true`.
|
|
76
|
+
|
|
77
|
+
WHY IT MATTERS
|
|
78
|
+
This job can fail every day and CI will still show green. The checkmark
|
|
79
|
+
on this workflow cannot be trusted.
|
|
80
|
+
|
|
81
|
+
HOW TO FIX
|
|
82
|
+
Remove continue-on-error, or scope it to individual non-blocking steps only.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Det er værdiens enhed: ikke en stilprik, men et sted, hvor dit CI
|
|
86
|
+
fortæller dig, at noget bestod, selvom det ikke gjorde.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## ⚡ Hurtig start
|
|
91
|
+
|
|
92
|
+
Kør den mod et repo for en fuld rapport og en værdighedsscore:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npx mjolnir-qa@latest
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**I CI er produktet én kommando.** Den scanner kun det, branchen rørte,
|
|
99
|
+
og afslutter med ikke-nul ved nye problemer:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx mjolnir-qa@latest --scope changed
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Smid det ind som et PR-check — `mjolnir ci install` skriver workflowet —
|
|
106
|
+
og du er færdig. Alt andet er valgfrit.
|
|
107
|
+
|
|
108
|
+
| Kommando | Hvad den gør |
|
|
109
|
+
| ----------------------------------- | -------------------------------------------------- |
|
|
110
|
+
| `mjolnir` | Scanning af hele repoet + værdighedsscore |
|
|
111
|
+
| `mjolnir --scope changed` | Kun det, din branch introducerede — CI-formen |
|
|
112
|
+
| `mjolnir ci install` | Genererer den vejledende PR-workflow |
|
|
113
|
+
| `mjolnir explain QA-CI-001` | Hvad / hvorfor / fix + målt FP-rate for én regel |
|
|
114
|
+
| `mjolnir rules --unmeasured` | Reglerne, der kører på antagelse, ikke måling |
|
|
115
|
+
| `mjolnir --json` / `--format sarif` | Maskinlæsbart / GitHub Code Scanning |
|
|
116
|
+
| `mjolnir --strict` | Kør også quarantine-tier-regler (højere FP-risiko) |
|
|
117
|
+
|
|
118
|
+
<details>
|
|
119
|
+
<summary><strong>Når noget er flaky</strong></summary>
|
|
120
|
+
|
|
121
|
+
| Kommando | Hvad den gør |
|
|
122
|
+
| ----------------------------------- | --------------------------------------------------------- |
|
|
123
|
+
| `mjolnir forensics ./test-results/` | Ægte kørselsdata → `TRUE-FLAKE`-domme, `FLAKY.md` |
|
|
124
|
+
| `mjolnir triage ./test-results/` | Karantæneforslag fra eksekveringshistorikken |
|
|
125
|
+
| `mjolnir pw-report ./test-results/` | Playwright-runoversigt — retries / flakes / de langsomste |
|
|
126
|
+
| `mjolnir doctor:playwright` | Deep scan kun Playwright + Selector Health Score |
|
|
127
|
+
|
|
128
|
+
</details>
|
|
129
|
+
|
|
130
|
+
<details>
|
|
131
|
+
<summary><strong>Lejlighedsvis / rapporter</strong></summary>
|
|
132
|
+
|
|
133
|
+
| Kommando | Hvad den gør |
|
|
134
|
+
| ------------------------------- | ------------------------------------------------------- |
|
|
135
|
+
| `mjolnir fix --dry-run` / `fix` | Sikre autofixes med bevis |
|
|
136
|
+
| `mjolnir baseline` / `diff` | Snapshot af fund, rapportér derefter kun nye/forværrede |
|
|
137
|
+
| `mjolnir impact --since <ref>` | Hvad der ændrede sig siden et tidligere commit |
|
|
138
|
+
| `mjolnir debt` | Testgældsregister med en kostmodel |
|
|
139
|
+
| `mjolnir handover` | Onboarding-kort over suiten til ny QA |
|
|
140
|
+
| `mjolnir stats` | Lokale all-time-tællere af sete fixes |
|
|
141
|
+
| `mjolnir badge` | shields.io-endpoint-JSON + snippet |
|
|
142
|
+
| `mjolnir rules --md` | Fuldt regelkatalog (JSON eller Markdown) |
|
|
143
|
+
| `mjolnir doctor` | Selvundersøgelse af Mjölnirs egen regelbase |
|
|
144
|
+
| `mjolnir create-rule <ID>` | Scaffold en ny regel + fixtures |
|
|
145
|
+
| `mjolnir --format mermaid` | Testarkitekturdiagram til en PR-kommentar |
|
|
146
|
+
|
|
147
|
+
</details>
|
|
148
|
+
|
|
149
|
+
Installér globalt i stedet for `npx`, hvis du foretrækker det:
|
|
150
|
+
`npm i -g mjolnir-qa`. Kræver Node.js ≥ 22.18. Virker på Windows, macOS
|
|
151
|
+
og Linux.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 👥 Hvem er det til?
|
|
156
|
+
|
|
157
|
+
- **QA / SDET**, der ejer en e2e- eller integrationsuite og har brug
|
|
158
|
+
for beviser for, at suiten faktisk fortjener den grønne check, den
|
|
159
|
+
producerer.
|
|
160
|
+
- **Platform-/DevEx-teams**, der har ansvaret for CI-integritet og
|
|
161
|
+
release gates — folkene, for hvem en `continue-on-error` aldrig må
|
|
162
|
+
male en rød pipeline grøn i stilhed.
|
|
163
|
+
- **OSS-maintainere**, der vil have en billig, altid aktiveret
|
|
164
|
+
verifikationsgate, der kører lokalt og i CI uden netværkskald.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 🔨 Hvad Mjölnir tjekker
|
|
169
|
+
|
|
170
|
+
| | |
|
|
171
|
+
| --- | ------------------------------------------------------------------------------------------------------------------- |
|
|
172
|
+
| ⚖️ | **Værdighedsscore** — ét tal, transparent fradragstabel, ingen black box |
|
|
173
|
+
| 🎭 | **Selector Health Score** — bedømmer dine Playwright-locators, ikke kun din pass rate |
|
|
174
|
+
| 🔬 | **Runtime-forundersøgelse** — læser ægte Playwright/JUnit-kørselsdata og fanger `TRUE-FLAKE`, ikke kun statiske gæt |
|
|
175
|
+
| 🚨 | **CI-integritetsregler** — fanger `continue-on-error`, `\|\| true` og andre falsk-grønne tricks |
|
|
176
|
+
| 🐍 | **Alle fire Playwright-bindings** — TypeScript, Python, Java, C#/.NET — plus pytest, JUnit/TestNG og CI-workflows |
|
|
177
|
+
| 🔒 | **Local-first** — nul netværkskald under scanning, nul telemetri, kører på sekunder |
|
|
178
|
+
|
|
179
|
+
### Reglerne
|
|
180
|
+
|
|
181
|
+
Hver regel leveres med både must-fire- **og** must-not-fire-fixtures.
|
|
182
|
+
En regel, der udløses på sin egen negative fixture, kan ikke skibes —
|
|
183
|
+
det er false-positive-firewallen.
|
|
184
|
+
|
|
185
|
+
<details>
|
|
186
|
+
<summary><strong>Testhygiejne</strong></summary>
|
|
187
|
+
|
|
188
|
+
| ID | Regel | Severity |
|
|
189
|
+
| ----------- | ---------------------------------------------------- | -------- |
|
|
190
|
+
| QA-TEST-001 | Committet fokuseret test (`.only`, `fit`) | error |
|
|
191
|
+
| QA-TEST-002 | Sprunget test uden begrundelse | error |
|
|
192
|
+
| QA-TEST-002 | Sprunget test med registreret begrundelse | warning |
|
|
193
|
+
| QA-TEST-003 | Test uden assertions | error |
|
|
194
|
+
| QA-TEST-004 | Hårdt sleep (`waitForTimeout`, `sleep()`, `delay()`) | warning |
|
|
195
|
+
| QA-TEST-006 | Retry-misbrug, der skjuler flakiness | warning |
|
|
196
|
+
| QA-TEST-010 | Tomt testlegeme | error |
|
|
197
|
+
|
|
198
|
+
</details>
|
|
199
|
+
|
|
200
|
+
<details>
|
|
201
|
+
<summary><strong>Testkvalitet</strong></summary>
|
|
202
|
+
|
|
203
|
+
| ID | Regel | Severity |
|
|
204
|
+
| ------------ | ------------------------------- | -------- |
|
|
205
|
+
| QA-TQUAL-001 | Kun-mock-verifikation | info |
|
|
206
|
+
| QA-TQUAL-002 | Tautologisk assertion | error |
|
|
207
|
+
| QA-TQUAL-009 | Assertion på promise uden await | error |
|
|
208
|
+
| QA-TQUAL-011 | Udkommenterede tests | warning |
|
|
209
|
+
|
|
210
|
+
</details>
|
|
211
|
+
|
|
212
|
+
<details>
|
|
213
|
+
<summary><strong>Playwright 🎭</strong></summary>
|
|
214
|
+
|
|
215
|
+
| ID | Regel | Severity |
|
|
216
|
+
| --------- | ---------------------------------------- | -------- |
|
|
217
|
+
| QA-PW-002 | Locator-assertion uden await | error |
|
|
218
|
+
| QA-PW-003 | `page.pause()` / `test.only()` committet | error |
|
|
219
|
+
| QA-PW-004 | Skrøbelige CSS/XPath-selectors | warning |
|
|
220
|
+
| QA-PW-005 | Forretningslogik i `page.evaluate()` | info |
|
|
221
|
+
| QA-PW-114 | Legacy element handles (`page.$`) | info |
|
|
222
|
+
| QA-PW-118 | `networkidle`-venten (flaky by design) | info |
|
|
223
|
+
| QA-PW-123 | Hårdkodede miljø-URL'er | warning |
|
|
224
|
+
|
|
225
|
+
</details>
|
|
226
|
+
|
|
227
|
+
<details>
|
|
228
|
+
<summary><strong>CI-integritet</strong></summary>
|
|
229
|
+
|
|
230
|
+
| ID | Regel | Severity |
|
|
231
|
+
| --------- | ----------------------------------------------------------------- | -------- |
|
|
232
|
+
| QA-CI-001 | `continue-on-error` maskerer fejl | error |
|
|
233
|
+
| QA-CI-002 | `\|\| true` synker exit-koder | error |
|
|
234
|
+
| QA-CI-005 | Rapport forbruges, men genereres aldrig | error |
|
|
235
|
+
| QA-CI-007 | Retry-wrappers omkring tests | warning |
|
|
236
|
+
| QA-CI-008 | Altid-succesfuldt step maskerer fejl | error |
|
|
237
|
+
| QA-CI-009 | Testens exit-kode propageres ikke (`\|` uden pipefail, `;`-kæder) | error |
|
|
238
|
+
| QA-CI-010 | Tests sprunget over, hvor de skal blokere (skip-on-PR-guards) | error |
|
|
239
|
+
|
|
240
|
+
</details>
|
|
241
|
+
|
|
242
|
+
<details>
|
|
243
|
+
<summary><strong>Python / pytest 🐍</strong></summary>
|
|
244
|
+
|
|
245
|
+
| ID | Regel | Severity |
|
|
246
|
+
| --------- | ------------------------------------------- | -------- |
|
|
247
|
+
| QA-PY-002 | Sprunget test (`skip`, ikke-strikt `xfail`) | warning |
|
|
248
|
+
| QA-PY-003 | Testfunktion uden assertions | error |
|
|
249
|
+
| QA-PY-005 | `time.sleep()` i tests | warning |
|
|
250
|
+
| QA-PY-006 | Tomt testlegeme (`pass`) | info |
|
|
251
|
+
| QA-PY-010 | Tilfældigheds-/tidsafhængighed uden freeze | info |
|
|
252
|
+
| QA-PY-012 | Tautologisk assertion | error |
|
|
253
|
+
|
|
254
|
+
20 Python-regler i alt (QA-PY-001…012 pytest-hygiejne + QA-PY-101…108 Playwright-Python).
|
|
255
|
+
|
|
256
|
+
</details>
|
|
257
|
+
|
|
258
|
+
<details>
|
|
259
|
+
<summary><strong>Java / JUnit · TestNG ☕</strong></summary>
|
|
260
|
+
|
|
261
|
+
| ID | Regel | Severity |
|
|
262
|
+
| --------- | -------------------------------------------- | -------- |
|
|
263
|
+
| QA-JV-101 | Deaktiveret test (`@Disabled`) | warning |
|
|
264
|
+
| QA-JV-102 | Hårdt sleep (`Thread.sleep()`) | warning |
|
|
265
|
+
| QA-JV-103 | Testmetode uden assertions | error |
|
|
266
|
+
| QA-JV-105 | Playwright hårdt sleep `waitForTimeout()` | warning |
|
|
267
|
+
| QA-JV-106 | Skrøbelig selector i stedet for role-locator | warning |
|
|
268
|
+
| QA-JV-108 | Hårdkodet miljø-URL i test | info |
|
|
269
|
+
| QA-JV-111 | Blanket-mock `page.route("**")` | info |
|
|
270
|
+
|
|
271
|
+
</details>
|
|
272
|
+
|
|
273
|
+
<details>
|
|
274
|
+
<summary><strong>C# / .NET — NUnit · xUnit · MSTest 🟣</strong></summary>
|
|
275
|
+
|
|
276
|
+
| ID | Regel | Severity |
|
|
277
|
+
| --------- | -------------------------------------------- | -------- |
|
|
278
|
+
| QA-CS-101 | Sprunget test (`[Ignore]`, `[Fact(Skip=)]`) | warning |
|
|
279
|
+
| QA-CS-102 | Hårdt sleep (`Thread.Sleep` / `Task.Delay`) | warning |
|
|
280
|
+
| QA-CS-103 | Testmetode uden assertions | error |
|
|
281
|
+
| QA-CS-105 | Hårdt sleep `WaitForTimeoutAsync()` | warning |
|
|
282
|
+
| QA-CS-106 | Skrøbelig selector i stedet for role-locator | warning |
|
|
283
|
+
| QA-CS-108 | Hårdkodet miljø-URL i test | info |
|
|
284
|
+
| QA-CS-111 | Blanket-mock `page.RouteAsync("**")` | info |
|
|
285
|
+
|
|
286
|
+
</details>
|
|
287
|
+
|
|
288
|
+
> Det fulde, levende katalog — hver regel med tier, confidence,
|
|
289
|
+
> false-positive-risiko og autofix-tilgængelighed — genereres fra
|
|
290
|
+
> registreren:
|
|
291
|
+
>
|
|
292
|
+
> ```bash
|
|
293
|
+
> mjolnir rules --md
|
|
294
|
+
> ```
|
|
295
|
+
>
|
|
296
|
+
> Sider pr. regel ligger under [`docs/rules/`](docs/rules/).
|
|
297
|
+
|
|
298
|
+
### Hvor meget er målt
|
|
299
|
+
|
|
300
|
+
**74 af 99 regler bærer en false-positive-rate målt mod rigtig OSS-kode**
|
|
301
|
+
(≥ 10 håndklassificerede fund hver; se
|
|
302
|
+
[docs/FP-AUDIT.md](docs/FP-AUDIT.md)). De andre 19 skiber på forfatterens
|
|
303
|
+
estimat. Hver scan-fodnote fortæller, hvor mange af de _udløste_ regler,
|
|
304
|
+
der er målt; `mjolnir rules --unmeasured` lister de uregistrerede; hver
|
|
305
|
+
regels `mjolnir explain`-side angiver dens status. Vi offentliggør
|
|
306
|
+
raten, selv når den er grim — QA-CS-103 auditeres til 95 % og er sat i
|
|
307
|
+
karantæne for det. At få de 78 til at vokse er projektets fortsatte
|
|
308
|
+
arbejde.
|
|
309
|
+
|
|
310
|
+
### Regel-tiers og sproglig modenhed
|
|
311
|
+
|
|
312
|
+
Hver regel er `core`, `extended` eller `quarantine`, tildelt ud fra sin
|
|
313
|
+
**målte** false-positive-rate:
|
|
314
|
+
|
|
315
|
+
| Tier | Betydning | Standardscan | `--strict` |
|
|
316
|
+
| ------------ | ----------------------------------------- | :----------: | :--------: |
|
|
317
|
+
| `core` | ≤ 10 % målt FP | ✅ | ✅ |
|
|
318
|
+
| `extended` | ≤ 30 % målt FP | ✅ | ✅ |
|
|
319
|
+
| `quarantine` | over 30 %, eller endnu ikke målt (n < 10) | ❌ | ✅ |
|
|
320
|
+
|
|
321
|
+
| Sprog | Adapter | Dækning i dag |
|
|
322
|
+
| --------------- | ------------ | ------------------------------------------------ |
|
|
323
|
+
| TypeScript / JS | Compiler-AST | bredeste, mest målte — mest `core`/`extended` |
|
|
324
|
+
| Python / pytest | Regex-lag | bredt, corpus-auditeret — mest `core`/`extended` |
|
|
325
|
+
| Java | Regex-lag | nyere — mest `extended`/`quarantine` |
|
|
326
|
+
| C# / .NET | Regex-lag | nyere — mest `extended`/`quarantine` |
|
|
327
|
+
|
|
328
|
+
TypeScript og Python har den bredeste målte dækning. Java og C# er
|
|
329
|
+
skibet, dokumenteret og holdes uden for overskriftstallet, indtil en
|
|
330
|
+
rigtig forbrugersuite (ikke et binding-biblioteks egne tests) er blevet
|
|
331
|
+
auditeret.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Sådan fungerer scoren
|
|
336
|
+
|
|
337
|
+
<p align="center">
|
|
338
|
+
<img src="assets/readme/terminal-hero.svg" alt="Mjölnir-terminaloutput — WORTHINESS 75/100 NEEDS WORK, en opdeling af diagnostik efter kategori og en FIX THIS FIRST-liste" width="820" />
|
|
339
|
+
</p>
|
|
340
|
+
|
|
341
|
+
<sub>Regenereres med `npm run docs:hero`;
|
|
342
|
+
[`tests/hero-asset-reproducibility.spec.ts`](tests/hero-asset-reproducibility.spec.ts)
|
|
343
|
+
får CI til at fejle, hvis artefaktet afviger fra, hvad reporteren
|
|
344
|
+
faktisk printer.</sub>
|
|
345
|
+
|
|
346
|
+
Scoren er transparent: **error −8, warning −3, info −1**, derefter
|
|
347
|
+
normaliseret efter suitens eksponering (fradrag pr. testdeklaration).
|
|
348
|
+
Bevisvægtede fradrag betyder, at svage signaler koster mindre.
|
|
349
|
+
Terminalen viser de samme diskonterede tal, som scoren bruger — ingen
|
|
350
|
+
black box. Fuld metode: [docs/SCORING.md](docs/SCORING.md).
|
|
351
|
+
|
|
352
|
+
**Domme**
|
|
353
|
+
|
|
354
|
+
| Score | Domme |
|
|
355
|
+
| ------- | ---------------- |
|
|
356
|
+
| ≥ 80 | ✓ **WORTHY** |
|
|
357
|
+
| 50 – 79 | ⚠ **NEEDS WORK** |
|
|
358
|
+
| < 50 | ✖ **UNWORTHY** |
|
|
359
|
+
|
|
360
|
+
**Bevisniveauer** — hvert fund bærer ét; det sætter fundets vægt i
|
|
361
|
+
scoren:
|
|
362
|
+
|
|
363
|
+
| Niveau | Betydning | Score-effekt | Eksempel |
|
|
364
|
+
| ------ | --------------------- | -------------- | -------------------------------------------------- |
|
|
365
|
+
| E2 | Deterministisk defekt | Fuldt fradrag | Committet `.only` — strukturelt beviseligt |
|
|
366
|
+
| E1 | Heuristisk mønster | Halvt fradrag | Regex-fundet `sleep()` — stærkt signal, ikke bevis |
|
|
367
|
+
| E0 | Iagttagelse | Nul (kun info) | Rapporteret, men gater aldrig CI eller trækker fra |
|
|
368
|
+
|
|
369
|
+
De fleste regler er **E1**. Slagordet „we prove it" henviser til dette
|
|
370
|
+
system: E2-fund er strukturelt bevis; E1-fund er korrekt positionerede
|
|
371
|
+
advarsler, ikke formelle beviser.
|
|
372
|
+
|
|
373
|
+
Et tomt repo scorer `null`, aldrig en falsk 100 — se
|
|
374
|
+
[Tillidsmodellen](#tillidsmodellen).
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## 🎭 Selector Health Score
|
|
379
|
+
|
|
380
|
+
Hovedmetrikken for Playwright-suiter — hvor robuste dine locators er:
|
|
381
|
+
|
|
382
|
+
```text
|
|
383
|
+
▚▞ SELECTOR HEALTH — e2e/checkout.spec.ts
|
|
384
|
+
|
|
385
|
+
[█████████████████░░░] 83 / 100
|
|
386
|
+
role/text: 2 · testid: 1 · css-chains: 1 ⚠ · xpath: 0
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
Rollebaserede locators får fuld score. CSS-klassekæder og XPath synker
|
|
390
|
+
scoren — de brækker ved enhver DOM-refaktor uden at fortælle dig,
|
|
391
|
+
hvilken adfærd der er regresseret.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## 🔬 Runtime-beviser
|
|
396
|
+
|
|
397
|
+
Statisk flakiness-detektion er gætteri. Mjölnir læser **ægte
|
|
398
|
+
eksekveringsdata** — Playwright JSON-rapporter og JUnit-XML fra enhver
|
|
399
|
+
runner:
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
mjolnir forensics ./test-results/
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
```text
|
|
406
|
+
▚▞ FLAKINESS LEADERBOARD
|
|
407
|
+
|
|
408
|
+
3 tests · 1 failed · 1 flaky · 1 retried
|
|
409
|
+
|
|
410
|
+
TRUE-FLAKE completes checkout with saved card (e2e/checkout.spec.ts)
|
|
411
|
+
████████████████████ 6.0s · 2 attempts
|
|
412
|
+
FAILING declines an expired card (e2e/checkout.spec.ts)
|
|
413
|
+
████░░░░░░░░░░░░░░░░ 1.1s · 1 attempt
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
En test, der kun består fra forsøg ≥ 2, er ikke en bestået test — det
|
|
417
|
+
er en heldig test. Den markeres `TRUE-FLAKE` uanset den endelige grønne
|
|
418
|
+
check.
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## ⚡ Mjölnir er ikke endnu en linter
|
|
423
|
+
|
|
424
|
+
Lintere fortæller dig, om koden følger regler. Mjölnir fortæller dig,
|
|
425
|
+
om din verifikation kan stoles på.
|
|
426
|
+
|
|
427
|
+
| | ESLint / SonarQube | Coverage-værktøjer | Manuelt review | **Mjölnir** |
|
|
428
|
+
| --------------------------------------------------------- | :----------------: | :----------------: | :------------: | :---------: |
|
|
429
|
+
| CI-workflow-integritet (`continue-on-error`, `\|\| true`) | ❌ | ❌ | sjældent | ✅ |
|
|
430
|
+
| På tværs af sprog (TS, Python, Java, C#) fra ét værktøj | ❌ | ❌ | ❌ | ✅ |
|
|
431
|
+
| Bedømmer Playwright-locators robusthed (Selector Health) | ❌ | ❌ | sjældent | ✅ |
|
|
432
|
+
| Markerer tests uden rigtige assertions | ✅ (plugin)\* | ❌ | nogle gange | ✅ |
|
|
433
|
+
| Fanger hårde sleeps (`waitForTimeout`, `time.sleep`) | ✅ (plugin)\* | ❌ | nogle gange | ✅ |
|
|
434
|
+
| Kører på sekunder, nul netværkskald under scanning | ✅ | ✅ | — | ✅ |
|
|
435
|
+
|
|
436
|
+
\*`eslint-plugin-jest` (`expect-expect`) og `eslint-plugin-playwright`
|
|
437
|
+
(`expect-expect`, `no-wait-for-timeout`) dækker dette for deres
|
|
438
|
+
respektive frameworks.
|
|
439
|
+
|
|
440
|
+
**Runtime-analyse** er en separat kategori ud over statisk lintning:
|
|
441
|
+
|
|
442
|
+
| | Playwright retry reporter | Allure / ReportPortal | **Mjölnir forensics** |
|
|
443
|
+
| ------------------------------------------------ | :-----------------------: | :-------------------: | :-------------------: |
|
|
444
|
+
| Læser ægte kørselsdata til `TRUE-FLAKE`-domme | delvist\* | delvist (tag) | ✅ |
|
|
445
|
+
| Flaky-triage-rapport fra eksekveringshistorikken | ❌ | ✅ | ✅ |
|
|
446
|
+
| Integrerer med den statiske værdighedsscore | ❌ | ❌ | ✅ |
|
|
447
|
+
|
|
448
|
+
\*Playwright sporer retries internt, men producerer ikke en selvstændig
|
|
449
|
+
flakiness-rapport med domme-etiketter.
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## 🤖 Hvorfor ikke bare bruge AI-kodereview?
|
|
454
|
+
|
|
455
|
+
Andet problem, andet lag. AI-review kan spotte en mistænkelig
|
|
456
|
+
testændring i en diff; det beviser ikke, at verifikationssystemet som
|
|
457
|
+
helhed er troværdigt — og det ser kun den diff, du viser det.
|
|
458
|
+
|
|
459
|
+
| | AI-kodereview (Copilot m.fl.) | **Mjölnir** |
|
|
460
|
+
| ------------------------------------------- | :-----------------------------------------: | :--------------------------: |
|
|
461
|
+
| Omkostning pr. scan | Tokens (skalerer med diff-størrelse) | **Nul** (lokal, installeret) |
|
|
462
|
+
| Ser hele suiten + alle CI-konfigs | Kun den PR-diff, du viser | **Alt, hver gang** |
|
|
463
|
+
| Deterministisk (samme input → samme output) | ❌ (ikke-deterministisk) | **✅** |
|
|
464
|
+
| Fanger mønstre, der har sovet i måneder | Kun hvis det er i konteksten | **✅** (scanner alle filer) |
|
|
465
|
+
| Husker fund mellem kørsler | ❌ (ingen hukommelse på tværs af sessioner) | **✅** (baseline + diff) |
|
|
466
|
+
| Kører uden menneskelig udløser | Kræver en PR eller prompt | **✅** (CI-hook, 3 sekunder) |
|
|
467
|
+
|
|
468
|
+
**Brug begge.** AI fanger nuance, intention og designfejl, ingen regex
|
|
469
|
+
kan finde. Mjölnir fanger de strukturelle mønstre, AI overser, fordi de
|
|
470
|
+
ser „intentionelle" ud — et committet `.only`, en opslugt exit-kode,
|
|
471
|
+
en `continue-on-error` på et testjob. Det er ikke bugs, der kræver
|
|
472
|
+
resonnement; det er fakta, der kræver scanning.
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
## 🤖 CI-integration
|
|
477
|
+
|
|
478
|
+
Én kommando genererer en PR-workflow — vejledende som standard, aldrig
|
|
479
|
+
blokerende:
|
|
480
|
+
|
|
481
|
+
```bash
|
|
482
|
+
mjolnir ci install
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Eller kobl den native ind i GitHub Code Scanning via SARIF:
|
|
486
|
+
|
|
487
|
+
```yaml
|
|
488
|
+
- run: npx mjolnir-qa@latest --format sarif > mjolnir.sarif
|
|
489
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
490
|
+
with:
|
|
491
|
+
sarif_file: mjolnir.sarif
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
Editor- og pipeline-opsætning til SARIF:
|
|
495
|
+
[docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md).
|
|
496
|
+
|
|
497
|
+
### Changed-scope-dækning
|
|
498
|
+
|
|
499
|
+
`--scope changed` tilskriver fund de linjer, din branch tilføjede i
|
|
500
|
+
forhold til merge-base med `main`. Den dækker testfiler (`*.spec.*`,
|
|
501
|
+
`*.test.*`) plus GitHub-workflowfiler og Playwright-konfigurationer i
|
|
502
|
+
diffen. Når merge-base ikke kan resolve — shallow clone, detached HEAD,
|
|
503
|
+
ikke-git-mål, anden default-branch — degraderer den ærligt: fund
|
|
504
|
+
falder tilbage til hel-fil-attribuering, og rapporten siger det.
|
|
505
|
+
Overskriv base-ref'en med `--base <ref>`.
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## Konfiguration
|
|
510
|
+
|
|
511
|
+
Mjölnir er zero-config. En valgfri `mjolnir.config.json` (eller
|
|
512
|
+
`.mjolnir.json`) i roden af repoet finjusterer severity, gating og
|
|
513
|
+
scope — den ændrer aldrig detektionssemantikken.
|
|
514
|
+
|
|
515
|
+
| Key | Type | Effekt |
|
|
516
|
+
| ------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
517
|
+
| `exclude` | `string[]` | Ekstra ignore-globs (gitignore-undersæt), oven på de indbyggede defaults |
|
|
518
|
+
| `gate` | `"advisory" \| "error" \| "warning"` | Hvilke severities der afslutter med ikke-nul (default `error`; `advisory` blokerer aldrig) |
|
|
519
|
+
| `severityOverrides` | `{ "<RULE-ID>": severity }` | Omrangordner en regels fund for dit repo |
|
|
520
|
+
| `ignore` | `IgnoreEntry[]` | Undertrykker fund — **`reason` er påkrævet**; indgange udløber efter 90 dage (en eksplicit `expires`-dato, eller config-filens last-modified-tid for indgange uden) |
|
|
521
|
+
| `plugins` | `string[]` | Regelpakker fra tredjepart (se [Tillidsmodellen](#tillidsmodellen)) |
|
|
522
|
+
|
|
523
|
+
```json
|
|
524
|
+
{
|
|
525
|
+
"gate": "error",
|
|
526
|
+
"exclude": ["legacy/**"],
|
|
527
|
+
"severityOverrides": { "QA-PW-118": "warning" },
|
|
528
|
+
"ignore": [
|
|
529
|
+
{
|
|
530
|
+
"ruleId": "QA-TEST-004",
|
|
531
|
+
"files": ["e2e/legacy-login.spec.ts"],
|
|
532
|
+
"reason": "Third-party widget needs a settle delay; tracked in JIRA-4821",
|
|
533
|
+
"expires": "2026-12-31"
|
|
534
|
+
}
|
|
535
|
+
]
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
- **`.mjolnirignore`** — en enkel gitignore-agtig fil til
|
|
540
|
+
sti-udelukkelser, samme dialekt som `exclude`. Brug den til
|
|
541
|
+
maskinspecifikt støj; brug `exclude`, når listen hører til i
|
|
542
|
+
versionsstyring sammen med resten af konfigurationen.
|
|
543
|
+
- **CLI-overrides** — `--strict` (inkluder karantæneregler),
|
|
544
|
+
`--width <cols>` og `--ascii` / `--no-ascii` (terminalrendering),
|
|
545
|
+
`--tone blunt` (knugere beskeder), `--max-duration <sec>` (begrænset
|
|
546
|
+
delvis scanning).
|
|
547
|
+
- Regelundertrykkelse og deprecation-levetid:
|
|
548
|
+
[docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md).
|
|
549
|
+
|
|
550
|
+
`ignore`-indgange driver også den selvstændige kommando
|
|
551
|
+
`mjolnir suppressions`, som lister, hvad der i øjeblikket er
|
|
552
|
+
undertrykket, og hvornår hver indgang udløber.
|
|
553
|
+
|
|
554
|
+
---
|
|
555
|
+
|
|
556
|
+
## 📐 Exit-koder & kontrakter
|
|
557
|
+
|
|
558
|
+
Frosne — trygge at bygge CI-logik på:
|
|
559
|
+
|
|
560
|
+
| Exit-kode | Betydning |
|
|
561
|
+
| --------- | -------------------------------------------------------------------- |
|
|
562
|
+
| `0` | Rent — ingen fund på eller over gaten |
|
|
563
|
+
| `1` | Fund på eller over gaten |
|
|
564
|
+
| `2` | Delvis scanning (tidsbudget ramt, ulæselige filer) — blokerer aldrig |
|
|
565
|
+
| `10` | Brugsfejl (ugyldigt flag, manglende mål) |
|
|
566
|
+
| `20` | Intern fejl |
|
|
567
|
+
|
|
568
|
+
JSON/SARIF-rapporten er `schemaVersion: 1`. Regel-ID'er
|
|
569
|
+
(`QA-<FAMILY>-NNN`) er uforanderlige, når de først er skibet, og
|
|
570
|
+
genbruges aldrig.
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
## Tillidsmodellen
|
|
575
|
+
|
|
576
|
+
- **Local-first** — nul netværkskald under scanning. Ever. Nul
|
|
577
|
+
telemetri.
|
|
578
|
+
- **Ingen falsk bevis** — vi siger hellere „ukendt" end „verificeret".
|
|
579
|
+
Et tomt repo får `score: null`, aldrig en falsk 100.
|
|
580
|
+
- **Delvis ærlighed** — hvis analysen blev afkortet, siger outputtet
|
|
581
|
+
det. Aldrig „complete", når det ikke er.
|
|
582
|
+
- **FP-firewall** — detektion kører på et kommentar-/string-frit view
|
|
583
|
+
af koden (TypeScript-regler bruger compiler-AST): et mønster inde i
|
|
584
|
+
en prosakommentar eller en doc-eksempelstreng er dokumentation, ikke
|
|
585
|
+
et fund.
|
|
586
|
+
- **Målt, ikke påstået** — kun regler med en false-positive-rate fra
|
|
587
|
+
rigtig OSS-kode skiber i overskriftstierne (se
|
|
588
|
+
[Hvor meget er målt](#hvor-meget-er-målt)); scan-fodnoten og
|
|
589
|
+
`mjolnir rules --unmeasured` fortæller dig, hvilke der er hvad.
|
|
590
|
+
- **Plugin-tillid** — plugins er npm-pakker deklareret under
|
|
591
|
+
`"plugins"`. Der er **ingen sandbox**: plugin-kode kører med fulde
|
|
592
|
+
Node-privilegier, samme tillidsmodel som ESLint- eller
|
|
593
|
+
Vitest-plugins. Core regel-ID-præfikser er reserverede og afvises
|
|
594
|
+
fra plugins mod spoofing.
|
|
595
|
+
- **Workspace-lokale eksterne regler** (mappebaserede, nul netværk) —
|
|
596
|
+
en `mjolnir-rules/`-mappe ved siden af scan-målet loader brugerdefinerede
|
|
597
|
+
regler: JSON-filer deklarerer regex-mønstre (ingen kode eksekveres),
|
|
598
|
+
`.mjs`/`.js`-moduler eksporterer `rules` (fuld Node-tillid, som
|
|
599
|
+
plugins). Eksterne regler bærer samme trust-metadata som core; de kan
|
|
600
|
+
aldrig skibe i core-tieren (core kræver en målt FP-rate fra
|
|
601
|
+
corpus-sidecaren — en deklareret `tier: "core"` klemmes til
|
|
602
|
+
`extended`), adlyder tier-grænser og tjekkes for drift:
|
|
603
|
+
`mjolnir rules --md --external` renderer kataloget fra de loadede
|
|
604
|
+
filer (proveniens `external`), og matrixgeneratoren accepterer
|
|
605
|
+
`--external <root>`.
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
## 🏗️ Arkitektur
|
|
610
|
+
|
|
611
|
+
<details>
|
|
612
|
+
<summary>Udvid træet</summary>
|
|
613
|
+
|
|
614
|
+
```
|
|
615
|
+
mjolnir/
|
|
616
|
+
├── src/
|
|
617
|
+
│ ├── engine/ # LanguageAdapter interface + rule runner
|
|
618
|
+
│ ├── adapters/ # typescript · python · java · csharp · github-actions
|
|
619
|
+
│ ├── rules/ # rules across 8 families + the measured-FP table
|
|
620
|
+
│ ├── playwright/ # Selector Health Score engine
|
|
621
|
+
│ ├── discovery/ # workspace, frameworks, ignore resolution
|
|
622
|
+
│ ├── scope/ # git merge-base changed-scope engine
|
|
623
|
+
│ ├── scorer/ # transparent deduction table + prioritization
|
|
624
|
+
│ ├── reporter/ # terminal · JSON · SARIF 2.1 · Mermaid
|
|
625
|
+
│ ├── forensics/ # run-data ingestion · flake verdicts · triage
|
|
626
|
+
│ ├── config/ # mjolnir.config.json + suppressions
|
|
627
|
+
│ ├── plugins/ # third-party rule loading (no sandbox)
|
|
628
|
+
│ └── commands/ # every subcommand
|
|
629
|
+
└── tests/
|
|
630
|
+
├── fixtures/ # must-fire / must-not-fire per rule
|
|
631
|
+
└── golden/ # frozen score regression locks
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
</details>
|
|
635
|
+
|
|
636
|
+
- **Regler er rene funktioner** — `(SourceFileContext) → Finding[]`,
|
|
637
|
+
ingen I/O, ingen globals. Nyt økosystem = én adapter + dens regler.
|
|
638
|
+
- **TypeScript/Playwright bruger compiler-AST** (ts-morph). Python,
|
|
639
|
+
Java og C# kører på et delt regex-lag med maskerede kommentare/strenge.
|
|
640
|
+
- Et tree-sitter WASM AST-lag til Java og C# findes og er næste
|
|
641
|
+
præcisionsskridt — det er endnu ikke koblet på den synkrone
|
|
642
|
+
scan-pipeline.
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
## 📚 Dokumentation
|
|
647
|
+
|
|
648
|
+
| Dokument | Hvad der er i det |
|
|
649
|
+
| ------------------------------------------------------ | ------------------------------------------- |
|
|
650
|
+
| [docs/SCORING.md](docs/SCORING.md) | Score-normalisering + bevisvægtning |
|
|
651
|
+
| [docs/FP-AUDIT.md](docs/FP-AUDIT.md) | Målte false-positive-rater + metode |
|
|
652
|
+
| [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md) | Regeltilstande, undertrykkelse, deprecation |
|
|
653
|
+
| [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md) | SARIF-output + editor/CI-opsætning |
|
|
654
|
+
| [docs/rules/](docs/rules/) | Genereret katalog pr. regel |
|
|
655
|
+
| [CONTRIBUTING.md](CONTRIBUTING.md) | Dev-opsætning + bidrag workflow |
|
|
656
|
+
| [CHANGELOG.md](CHANGELOG.md) | Releasehistorik |
|
|
657
|
+
| [SECURITY.md](SECURITY.md) | Sårbarhedsrapportering |
|
|
658
|
+
|
|
659
|
+
---
|
|
660
|
+
|
|
661
|
+
## 📈 Status
|
|
662
|
+
|
|
663
|
+
**v0.5.x · åben beta.** JSON-skemaet og exit-koderne er frosne
|
|
664
|
+
kontrakter. TypeScript og Python har den bredeste målte dækning; Java
|
|
665
|
+
og C# er nyere — læs dem gennem
|
|
666
|
+
[tiers-tabellen](#regel-tiers-og-sproglig-modenhed).
|
|
667
|
+
|
|
668
|
+
---
|
|
669
|
+
|
|
670
|
+
## 🤝 Bidrag
|
|
671
|
+
|
|
672
|
+
Nye regler er den nemmeste første bidrag — én kommando scaffolder
|
|
673
|
+
reglen plus dens must-fire- **og** must-not-fire-fixtures (den
|
|
674
|
+
genererede regel fejler bevidst dens fixtures, indtil du implementerer
|
|
675
|
+
rigtig detektion — en stub kan ikke skibes):
|
|
676
|
+
|
|
677
|
+
```bash
|
|
678
|
+
mjolnir create-rule QA-PW-140 --title "Screenshot without diff bound"
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
Fuld dev-opsætning, standing-gate-kommandoerne og anti-creep- /
|
|
682
|
+
fixture-firewall-lovene er i [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
683
|
+
|
|
684
|
+
---
|
|
685
|
+
|
|
686
|
+
<div align="center">
|
|
687
|
+
|
|
688
|
+
**Stop med at skibe tests, du ikke kan stole på.**
|
|
689
|
+
|
|
690
|
+
```bash
|
|
691
|
+
npx mjolnir-qa@latest
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
**Star ⭐ · Watch 👀 · Contribute 🤝**
|
|
695
|
+
|
|
696
|
+
Bygget af [Sergey Bar](https://www.linkedin.com/in/sergeybar/)
|
|
697
|
+
|
|
698
|
+
</div>
|