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.no.md
ADDED
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="assets/readme/logo.png" alt="Mjölnir — Verification Trust Engine" width="800" />
|
|
4
|
+
|
|
5
|
+
### Testene dine lyver til deg. Vi beviser det.
|
|
6
|
+
|
|
7
|
+
**Verification Trust Engine for QA.** Mjölnir auditor testsuiter og
|
|
8
|
+
CI-pipelines, rapporterer en verdighetsscore og viser nøyaktig hvor
|
|
9
|
+
tilliten bryter sammen.
|
|
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.da.md) | [日本語](README.ja.md) | [Polski](README.pl.md) | [Русский](README.ru.md) | Norsk | [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 testene dine verd tillit?**
|
|
25
|
+
|
|
26
|
+
[Se det virke](#-se-det-virke) ·
|
|
27
|
+
[Rask start](#-rask-start) ·
|
|
28
|
+
[Hva den sjekker](#-hva-mjölnir-sjekker) ·
|
|
29
|
+
[Scoring](#slik-fungerer-scoren) ·
|
|
30
|
+
[CI](#-ci-integrasjon) · [Konfigurasjon](#konfigurasjon) ·
|
|
31
|
+
[Dokumentasjon](#-dokumentasjon)
|
|
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 komplette --verbose-rapport over et demo-repo: WORTHINESS 75/100 NEEDS WORK, en oppdeling av diagnostikk etter kategori, en FIX THIS FIRST-liste og hvert funn med regel-ID og linjenummer på tvers av CI-, Playwright-, testhygiene- og Python-regler" width="900" />
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<sub>Det komplette `npx mjolnir-qa ./examples/demo-repo --verbose`-resultatet,
|
|
44
|
+
renderet av den ekte reporteren — ingenting klippet vekk. Regenereres
|
|
45
|
+
med `npm run docs:demo`;
|
|
46
|
+
[`tests/demo-asset-reproducibility.spec.ts`](tests/demo-asset-reproducibility.spec.ts)
|
|
47
|
+
får CI til å feile hvis artefakten avviker fra det verktøyet skriver
|
|
48
|
+
ut.</sub>
|
|
49
|
+
|
|
50
|
+
**Hva som nettopp skjedde:**
|
|
51
|
+
|
|
52
|
+
1. Mjölnir oppdaget Playwright-specs, konfigurasjonen, CI-workflowen og
|
|
53
|
+
en Python-testfil — fire språk/formater, ett gjennomløp.
|
|
54
|
+
2. Den fant beviser som svekker tilliten til suiten — en
|
|
55
|
+
`continue-on-error` som maskerer en jobb, en `|| true` som svelger en
|
|
56
|
+
exit-kode, harde sleeps, en skjør selector, hardkodede staging-URLer,
|
|
57
|
+
en `networkidle`-ventetid.
|
|
58
|
+
3. Den gjorde hvert av dem til et konkret funn med regel-ID, plassering
|
|
59
|
+
og fiks — og til én score du kan gate en PR på.
|
|
60
|
+
|
|
61
|
+
### Ett funn på nært hold
|
|
62
|
+
|
|
63
|
+
Kjør `mjolnir explain QA-CI-001` på det første funnet over, og du får:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
▚▞ QA-CI-001 — continue-on-error masks a failing verification gate
|
|
67
|
+
|
|
68
|
+
Severity: error
|
|
69
|
+
Confidence: high
|
|
70
|
+
Evidence: E2
|
|
71
|
+
Measured FP: not yet measured — this rule ships on assumption (see docs/FP-AUDIT.md)
|
|
72
|
+
|
|
73
|
+
WHAT WAS FOUND (real detector output, not a mockup)
|
|
74
|
+
Job `security-scan` runs a verification gate under `continue-on-error: true`.
|
|
75
|
+
|
|
76
|
+
WHY IT MATTERS
|
|
77
|
+
This job can fail every day and CI will still show green. The checkmark
|
|
78
|
+
on this workflow cannot be trusted.
|
|
79
|
+
|
|
80
|
+
HOW TO FIX
|
|
81
|
+
Remove continue-on-error, or scope it to individual non-blocking steps only.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Det er verdiens enhet: ikke en stilprikke, men et sted der CI-en din
|
|
85
|
+
forteller deg at noe besto, selv om det ikke gjorde det.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## ⚡ Rask start
|
|
90
|
+
|
|
91
|
+
Kjør den mot et repo for en full rapport og en verdighetsscore:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npx mjolnir-qa@latest
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**I CI er produktet én kommando.** Den skanner bare det branchen rørte
|
|
98
|
+
og avslutter med ikke-null ved nye problemer:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npx mjolnir-qa@latest --scope changed
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Legg det inn som en PR-sjekk — `mjolnir ci install` skriver workflowen —
|
|
105
|
+
og du er ferdig. Alt annet er valgfritt.
|
|
106
|
+
|
|
107
|
+
| Kommando | Hva den gjør |
|
|
108
|
+
| ----------------------------------- | --------------------------------------------------- |
|
|
109
|
+
| `mjolnir` | Skann av hele repoet + verdighetsscore |
|
|
110
|
+
| `mjolnir --scope changed` | Bare det branchen din innførte — CI-formen |
|
|
111
|
+
| `mjolnir ci install` | Genererer den rådgivende PR-workflowen |
|
|
112
|
+
| `mjolnir explain QA-CI-001` | Hva / hvorfor / fiks + målt FP-rate for én regel |
|
|
113
|
+
| `mjolnir rules --unmeasured` | Reglene som kjører på antagelse, ikke måling |
|
|
114
|
+
| `mjolnir --json` / `--format sarif` | Maskinlesbart / GitHub Code Scanning |
|
|
115
|
+
| `mjolnir --strict` | Kjør også quarantine-tier-regler (høyere FP-risiko) |
|
|
116
|
+
|
|
117
|
+
<details>
|
|
118
|
+
<summary><strong>Når noe er flaky</strong></summary>
|
|
119
|
+
|
|
120
|
+
| Kommando | Hva den gjør |
|
|
121
|
+
| ----------------------------------- | ------------------------------------------------------- |
|
|
122
|
+
| `mjolnir forensics ./test-results/` | Ekte kjøringsdata → `TRUE-FLAKE`-dommer, `FLAKY.md` |
|
|
123
|
+
| `mjolnir triage ./test-results/` | Karanteneforslag fra utførelseshistorikken |
|
|
124
|
+
| `mjolnir pw-report ./test-results/` | Playwright-runoversikt — retries / flakes / de tregeste |
|
|
125
|
+
| `mjolnir doctor:playwright` | Dypskann kun Playwright + Selector Health Score |
|
|
126
|
+
|
|
127
|
+
</details>
|
|
128
|
+
|
|
129
|
+
<details>
|
|
130
|
+
<summary><strong>Av og til / rapporter</strong></summary>
|
|
131
|
+
|
|
132
|
+
| Kommando | Hva den gjør |
|
|
133
|
+
| ------------------------------- | -------------------------------------------------------- |
|
|
134
|
+
| `mjolnir fix --dry-run` / `fix` | Trygge autofikser med bevis |
|
|
135
|
+
| `mjolnir baseline` / `diff` | Øyeblikksbilde av funn, rapporter så bare nye/forverrede |
|
|
136
|
+
| `mjolnir impact --since <ref>` | Hva som endret seg siden et tidligere commit |
|
|
137
|
+
| `mjolnir debt` | Testgjeldsregister med en kostnadsmodell |
|
|
138
|
+
| `mjolnir handover` | Onboarding-kart over suiten for ny QA |
|
|
139
|
+
| `mjolnir stats` | Lokale all-time-tellere av sette fikser |
|
|
140
|
+
| `mjolnir badge` | shields.io-endepunkt-JSON + snippet |
|
|
141
|
+
| `mjolnir rules --md` | Fullstendig regelkatalog (JSON eller Markdown) |
|
|
142
|
+
| `mjolnir doctor` | Selvaudit av Mjölnirs egen regelbase |
|
|
143
|
+
| `mjolnir create-rule <ID>` | Scaffold en ny regel + fixtures |
|
|
144
|
+
| `mjolnir --format mermaid` | Testarkitekturdiagram til en PR-kommentar |
|
|
145
|
+
|
|
146
|
+
</details>
|
|
147
|
+
|
|
148
|
+
Installer globalt i stedet for `npx` hvis du foretrekker det:
|
|
149
|
+
`npm i -g mjolnir-qa`. Krever Node.js ≥ 22.18. Fungerer på Windows,
|
|
150
|
+
macOS og Linux.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 👥 Hvem er det for?
|
|
155
|
+
|
|
156
|
+
- **QA / SDET** som eier en e2e- eller integrasjonssuite og trenger
|
|
157
|
+
bevis for at suiten faktisk fortjener den grønne haken den
|
|
158
|
+
produserer.
|
|
159
|
+
- **Plattform-/DevEx-team** som har ansvar for CI-integritet og
|
|
160
|
+
release gates — folket som bryr seg om at en `continue-on-error`
|
|
161
|
+
aldri stille maler en rød pipeline grønn.
|
|
162
|
+
- **OSS-maintainere** som vil ha en billig, alltid på verifikasjonsgate
|
|
163
|
+
som kjører lokalt og i CI uten nettverkskall.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## 🔨 Hva Mjölnir sjekker
|
|
168
|
+
|
|
169
|
+
| | |
|
|
170
|
+
| --- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
171
|
+
| ⚖️ | **Verdighetsscore** — ett tall, transparent fradragstabell, ingen black box |
|
|
172
|
+
| 🎭 | **Selector Health Score** — vurderer Playwright-locatorene dine, ikke bare pass rate |
|
|
173
|
+
| 🔬 | **Runtime-forundersøkelse** — leser ekte Playwright/JUnit-kjøringsdata og fanger `TRUE-FLAKE`, ikke bare statiske gjetninger |
|
|
174
|
+
| 🚨 | **CI-integritetsregler** — fanger `continue-on-error`, `\|\| true` og andre falskgrønne triks |
|
|
175
|
+
| 🐍 | **Alle fire Playwright-bindings** — TypeScript, Python, Java, C#/.NET — pluss pytest, JUnit/TestNG og CI-workflows |
|
|
176
|
+
| 🔒 | **Local-first** — null nettverkskall under skanning, null telemetri, kjører på sekunder |
|
|
177
|
+
|
|
178
|
+
### Reglene
|
|
179
|
+
|
|
180
|
+
Hver regel leveres med både must-fire- **og** must-not-fire-fixtures.
|
|
181
|
+
En regel som utløses på sin egen negative fixture, kan ikke skipes —
|
|
182
|
+
det er false-positive-brannmuren.
|
|
183
|
+
|
|
184
|
+
<details>
|
|
185
|
+
<summary><strong>Testhygiene</strong></summary>
|
|
186
|
+
|
|
187
|
+
| ID | Regel | Severity |
|
|
188
|
+
| ----------- | ---------------------------------------------------- | -------- |
|
|
189
|
+
| QA-TEST-001 | Commitet fokusert test (`.only`, `fit`) | error |
|
|
190
|
+
| QA-TEST-002 | Hoppet over test uten begrunnelse | error |
|
|
191
|
+
| QA-TEST-002 | Hoppet over test med registrert begrunnelse | warning |
|
|
192
|
+
| QA-TEST-003 | Test uten assertions | error |
|
|
193
|
+
| QA-TEST-004 | Hardt sleep (`waitForTimeout`, `sleep()`, `delay()`) | warning |
|
|
194
|
+
| QA-TEST-006 | Retry-misbruk som skjuler flakiness | warning |
|
|
195
|
+
| QA-TEST-010 | Tomt testkropp | error |
|
|
196
|
+
|
|
197
|
+
</details>
|
|
198
|
+
|
|
199
|
+
<details>
|
|
200
|
+
<summary><strong>Testkvalitet</strong></summary>
|
|
201
|
+
|
|
202
|
+
| ID | Regel | Severity |
|
|
203
|
+
| ------------ | ------------------------------- | -------- |
|
|
204
|
+
| QA-TQUAL-001 | Kun-mock-verifisering | info |
|
|
205
|
+
| QA-TQUAL-002 | Tautologisk assertion | error |
|
|
206
|
+
| QA-TQUAL-009 | Assertion på promise uten await | error |
|
|
207
|
+
| QA-TQUAL-011 | Utkommenterte tester | warning |
|
|
208
|
+
|
|
209
|
+
</details>
|
|
210
|
+
|
|
211
|
+
<details>
|
|
212
|
+
<summary><strong>Playwright 🎭</strong></summary>
|
|
213
|
+
|
|
214
|
+
| ID | Regel | Severity |
|
|
215
|
+
| --------- | ---------------------------------------- | -------- |
|
|
216
|
+
| QA-PW-002 | Locator-assertion uten await | error |
|
|
217
|
+
| QA-PW-003 | `page.pause()` / `test.only()` commitet | error |
|
|
218
|
+
| QA-PW-004 | Skjøre CSS/XPath-selektorer | warning |
|
|
219
|
+
| QA-PW-005 | Forretningslogikk i `page.evaluate()` | info |
|
|
220
|
+
| QA-PW-114 | Legacy element handles (`page.$`) | info |
|
|
221
|
+
| QA-PW-118 | `networkidle`-ventetid (flaky by design) | info |
|
|
222
|
+
| QA-PW-123 | Hardkodede miljø-URLer | warning |
|
|
223
|
+
|
|
224
|
+
</details>
|
|
225
|
+
|
|
226
|
+
<details>
|
|
227
|
+
<summary><strong>CI-integritet</strong></summary>
|
|
228
|
+
|
|
229
|
+
| ID | Regel | Severity |
|
|
230
|
+
| --------- | ------------------------------------------------------------------ | -------- |
|
|
231
|
+
| QA-CI-001 | `continue-on-error` maskerer feil | error |
|
|
232
|
+
| QA-CI-002 | `\|\| true` svelger exit-koder | error |
|
|
233
|
+
| QA-CI-005 | Rapport forbrukes, men genereres aldri | error |
|
|
234
|
+
| QA-CI-007 | Retry-wrappers rundt tester | warning |
|
|
235
|
+
| QA-CI-008 | Alltid-vellykket step maskerer feil | error |
|
|
236
|
+
| QA-CI-009 | Testens exit-kode propageres ikke (`\|` uten pipefail, `;`-kjeder) | error |
|
|
237
|
+
| QA-CI-010 | Tester hoppes over der de må blokkere (skip-on-PR-guards) | error |
|
|
238
|
+
|
|
239
|
+
</details>
|
|
240
|
+
|
|
241
|
+
<details>
|
|
242
|
+
<summary><strong>Python / pytest 🐍</strong></summary>
|
|
243
|
+
|
|
244
|
+
| ID | Regel | Severity |
|
|
245
|
+
| --------- | ---------------------------------------------- | -------- |
|
|
246
|
+
| QA-PY-002 | Hoppet over test (`skip`, ikke-strikt `xfail`) | warning |
|
|
247
|
+
| QA-PY-003 | Testfunksjon uten assertions | error |
|
|
248
|
+
| QA-PY-005 | `time.sleep()` i tester | warning |
|
|
249
|
+
| QA-PY-006 | Tomt testkropp (`pass`) | info |
|
|
250
|
+
| QA-PY-010 | Tilfeldighets-/tidsavhengighet uten freeze | info |
|
|
251
|
+
| QA-PY-012 | Tautologisk assertion | error |
|
|
252
|
+
|
|
253
|
+
20 Python-regler totalt (QA-PY-001…012 pytest-hygiene + QA-PY-101…108 Playwright-Python).
|
|
254
|
+
|
|
255
|
+
</details>
|
|
256
|
+
|
|
257
|
+
<details>
|
|
258
|
+
<summary><strong>Java / JUnit · TestNG ☕</strong></summary>
|
|
259
|
+
|
|
260
|
+
| ID | Regel | Severity |
|
|
261
|
+
| --------- | ----------------------------------------- | -------- |
|
|
262
|
+
| QA-JV-101 | Deaktivert test (`@Disabled`) | warning |
|
|
263
|
+
| QA-JV-102 | Hardt sleep (`Thread.sleep()`) | warning |
|
|
264
|
+
| QA-JV-103 | Testmetode uten assertions | error |
|
|
265
|
+
| QA-JV-105 | Playwright hardt sleep `waitForTimeout()` | warning |
|
|
266
|
+
| QA-JV-106 | Skjør selector i stedet for role-locator | warning |
|
|
267
|
+
| QA-JV-108 | Hardkodet miljø-URL i test | info |
|
|
268
|
+
| QA-JV-111 | Blanket-mock `page.route("**")` | info |
|
|
269
|
+
|
|
270
|
+
</details>
|
|
271
|
+
|
|
272
|
+
<details>
|
|
273
|
+
<summary><strong>C# / .NET — NUnit · xUnit · MSTest 🟣</strong></summary>
|
|
274
|
+
|
|
275
|
+
| ID | Regel | Severity |
|
|
276
|
+
| --------- | ---------------------------------------------- | -------- |
|
|
277
|
+
| QA-CS-101 | Hoppet over test (`[Ignore]`, `[Fact(Skip=)]`) | warning |
|
|
278
|
+
| QA-CS-102 | Hardt sleep (`Thread.Sleep` / `Task.Delay`) | warning |
|
|
279
|
+
| QA-CS-103 | Testmetode uten assertions | error |
|
|
280
|
+
| QA-CS-105 | Hardt sleep `WaitForTimeoutAsync()` | warning |
|
|
281
|
+
| QA-CS-106 | Skjør selector i stedet for role-locator | warning |
|
|
282
|
+
| QA-CS-108 | Hardkodet miljø-URL i test | info |
|
|
283
|
+
| QA-CS-111 | Blanket-mock `page.RouteAsync("**")` | info |
|
|
284
|
+
|
|
285
|
+
</details>
|
|
286
|
+
|
|
287
|
+
> Den fulle, levende katalogen — hver regel med tier, confidence,
|
|
288
|
+
> false-positive-risiko og autofix-tilgjengelighet — genereres fra
|
|
289
|
+
> registret:
|
|
290
|
+
>
|
|
291
|
+
> ```bash
|
|
292
|
+
> mjolnir rules --md
|
|
293
|
+
> ```
|
|
294
|
+
>
|
|
295
|
+
> Sider per regel ligger under [`docs/rules/`](docs/rules/).
|
|
296
|
+
|
|
297
|
+
### Hvor mye er målt
|
|
298
|
+
|
|
299
|
+
**74 av 99 regler bærer en false-positive-rate målt mot ekte OSS-kode**
|
|
300
|
+
(≥ 10 håndklassifiserte funn hver; se
|
|
301
|
+
[docs/FP-AUDIT.md](docs/FP-AUDIT.md)). De andre 19 skiper på
|
|
302
|
+
forfatterens estimat. Hver skann-fotnote forteller hvor mange av de
|
|
303
|
+
_utløste_ reglene som er målt; `mjolnir rules --unmeasured` lister de
|
|
304
|
+
umålte; hver regels `mjolnir explain`-side angir statusen. Vi publiserer
|
|
305
|
+
raten selv når den er stygg — QA-CS-103 auditeres til 95 % og er satt i
|
|
306
|
+
karantene for det. Å få de 78 til å vokse er prosjektets fortsatte
|
|
307
|
+
arbeid.
|
|
308
|
+
|
|
309
|
+
### Regel-tiers og språkmodenhet
|
|
310
|
+
|
|
311
|
+
Hver regel er `core`, `extended` eller `quarantine`, tildelt ut fra sin
|
|
312
|
+
**målte** false-positive-rate:
|
|
313
|
+
|
|
314
|
+
| Tier | Betydning | Standardskann | `--strict` |
|
|
315
|
+
| ------------ | ---------------------------------------- | :-----------: | :--------: |
|
|
316
|
+
| `core` | ≤ 10 % målt FP | ✅ | ✅ |
|
|
317
|
+
| `extended` | ≤ 30 % målt FP | ✅ | ✅ |
|
|
318
|
+
| `quarantine` | over 30 %, eller ennå ikke målt (n < 10) | ❌ | ✅ |
|
|
319
|
+
|
|
320
|
+
| Språk | Adapter | Dekning i dag |
|
|
321
|
+
| --------------- | -------------- | ------------------------------------------------ |
|
|
322
|
+
| TypeScript / JS | Kompilator-AST | bredeste, mest målte — mest `core`/`extended` |
|
|
323
|
+
| Python / pytest | Regex-lag | bredt, corpus-auditeret — mest `core`/`extended` |
|
|
324
|
+
| Java | Regex-lag | nyere — mest `extended`/`quarantine` |
|
|
325
|
+
| C# / .NET | Regex-lag | nyere — mest `extended`/`quarantine` |
|
|
326
|
+
|
|
327
|
+
TypeScript og Python har den bredeste målte dekningen. Java og C# er
|
|
328
|
+
skipet, dokumentert og holdes utenfor overskriftstallet til en ekte
|
|
329
|
+
forbrukersuite (ikke et binding-biblioteks egne tester) er auditeret.
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Slik fungerer scoren
|
|
334
|
+
|
|
335
|
+
<p align="center">
|
|
336
|
+
<img src="assets/readme/terminal-hero.svg" alt="Mjölnir-terminalutskrift — WORTHINESS 75/100 NEEDS WORK, en oppdeling av diagnostikk etter kategori og en FIX THIS FIRST-liste" width="820" />
|
|
337
|
+
</p>
|
|
338
|
+
|
|
339
|
+
<sub>Regenereres med `npm run docs:hero`;
|
|
340
|
+
[`tests/hero-asset-reproducibility.spec.ts`](tests/hero-asset-reproducibility.spec.ts)
|
|
341
|
+
får CI til å feile hvis artefakten avviker fra det reporteren faktisk
|
|
342
|
+
skriver ut.</sub>
|
|
343
|
+
|
|
344
|
+
Scoren er transparent: **error −8, warning −3, info −1**, deretter
|
|
345
|
+
normalisert etter suitens eksponering (fradrag per testdeklarasjon).
|
|
346
|
+
Bevisvektede fradrag betyr at svake signaler koster mindre. Terminalen
|
|
347
|
+
viser de samme diskonterte tallene scoren bruker — ingen black box.
|
|
348
|
+
Full metode: [docs/SCORING.md](docs/SCORING.md).
|
|
349
|
+
|
|
350
|
+
**Dommer**
|
|
351
|
+
|
|
352
|
+
| Score | Domme |
|
|
353
|
+
| ------- | ---------------- |
|
|
354
|
+
| ≥ 80 | ✓ **WORTHY** |
|
|
355
|
+
| 50 – 79 | ⚠ **NEEDS WORK** |
|
|
356
|
+
| < 50 | ✖ **UNWORTHY** |
|
|
357
|
+
|
|
358
|
+
**Bevisnivåer** — hvert funn bærer ett; det setter funnets vekt i
|
|
359
|
+
scoren:
|
|
360
|
+
|
|
361
|
+
| Nivå | Betydning | Score-effekt | Eksempel |
|
|
362
|
+
| ---- | --------------------- | --------------- | --------------------------------------------------- |
|
|
363
|
+
| E2 | Deterministisk defekt | Fullt fradrag | Commitet `.only` — strukturelt bevisbart |
|
|
364
|
+
| E1 | Heuristisk mønster | Halvt fradrag | Regex-truffet `sleep()` — sterkt signal, ikke bevis |
|
|
365
|
+
| E0 | Observasjon | Null (kun info) | Rapportert, men gater aldri CI eller trekker fra |
|
|
366
|
+
|
|
367
|
+
De fleste regler er **E1**. Slagordet «we prove it» viser til dette
|
|
368
|
+
systemet: E2-funn er strukturelt bevis; E1-funn er korrekt plasserte
|
|
369
|
+
advarsler, ikke formelle beviser.
|
|
370
|
+
|
|
371
|
+
Et tomt repo scorer `null`, aldri en falsk 100 — se
|
|
372
|
+
[Tillitsmodellen](#tillitsmodellen).
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## 🎭 Selector Health Score
|
|
377
|
+
|
|
378
|
+
Hovedmetrikken for Playwright-suiter — hvor robuste locatorene dine er:
|
|
379
|
+
|
|
380
|
+
```text
|
|
381
|
+
▚▞ SELECTOR HEALTH — e2e/checkout.spec.ts
|
|
382
|
+
|
|
383
|
+
[█████████████████░░░] 83 / 100
|
|
384
|
+
role/text: 2 · testid: 1 · css-chains: 1 ⚠ · xpath: 0
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Rollebaserte locators får full score. CSS-klassekjeder og XPath senker
|
|
388
|
+
scoren — de brekker ved enhver DOM-refaktor uten å fortelle deg hvilken
|
|
389
|
+
atferd som har regressert.
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## 🔬 Runtime-bevis
|
|
394
|
+
|
|
395
|
+
Statisk flakiness-deteksjon er gjetting. Mjölnir leser **ekte
|
|
396
|
+
kjøringsdata** — Playwright JSON-rapporter og JUnit-XML fra enhver
|
|
397
|
+
runner:
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
mjolnir forensics ./test-results/
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
```text
|
|
404
|
+
▚▞ FLAKINESS LEADERBOARD
|
|
405
|
+
|
|
406
|
+
3 tests · 1 failed · 1 flaky · 1 retried
|
|
407
|
+
|
|
408
|
+
TRUE-FLAKE completes checkout with saved card (e2e/checkout.spec.ts)
|
|
409
|
+
████████████████████ 6.0s · 2 attempts
|
|
410
|
+
FAILING declines an expired card (e2e/checkout.spec.ts)
|
|
411
|
+
████░░░░░░░░░░░░░░░░ 1.1s · 1 attempt
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
En test som bare består fra forsøk ≥ 2, er ikke en bestått test — det
|
|
415
|
+
er en heldig test. Den merkes `TRUE-FLAKE` uansett den endelige grønne
|
|
416
|
+
haken.
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## ⚡ Mjölnir er ikke enda en linter
|
|
421
|
+
|
|
422
|
+
Lintere forteller deg om koden følger regler. Mjölnir forteller deg om
|
|
423
|
+
verifiseringen din kan stoles på.
|
|
424
|
+
|
|
425
|
+
| | ESLint / SonarQube | Coverage-verktøy | Manuell review | **Mjölnir** |
|
|
426
|
+
| -------------------------------------------------------------- | :----------------: | :--------------: | :------------: | :---------: |
|
|
427
|
+
| CI-workflow-integritet (`continue-on-error`, `\|\| true`) | ❌ | ❌ | sjelden | ✅ |
|
|
428
|
+
| Tverrspråklig (TS, Python, Java, C#) fra ett verktøy | ❌ | ❌ | ❌ | ✅ |
|
|
429
|
+
| Vurderer robustheten til Playwright-locators (Selector Health) | ❌ | ❌ | sjelden | ✅ |
|
|
430
|
+
| Markerer tester uten ekte assertions | ✅ (plugin)\* | ❌ | av og til | ✅ |
|
|
431
|
+
| Fanger harde sleeps (`waitForTimeout`, `time.sleep`) | ✅ (plugin)\* | ❌ | av og til | ✅ |
|
|
432
|
+
| Kjører på sekunder, null nettverkskall under skanning | ✅ | ✅ | — | ✅ |
|
|
433
|
+
|
|
434
|
+
\*`eslint-plugin-jest` (`expect-expect`) og `eslint-plugin-playwright`
|
|
435
|
+
(`expect-expect`, `no-wait-for-timeout`) dekker dette for sine
|
|
436
|
+
respektive rammeverk.
|
|
437
|
+
|
|
438
|
+
**Runtime-analyse** er en egen kategori ved siden av statisk linting:
|
|
439
|
+
|
|
440
|
+
| | Playwright retry reporter | Allure / ReportPortal | **Mjölnir forensics** |
|
|
441
|
+
| ----------------------------------------------- | :-----------------------: | :-------------------: | :-------------------: |
|
|
442
|
+
| Leser ekte kjøringsdata til `TRUE-FLAKE`-dommer | delvis\* | delvis (tag) | ✅ |
|
|
443
|
+
| Flaky-triage-rapport fra utførelseshistorikken | ❌ | ✅ | ✅ |
|
|
444
|
+
| Integrerer med den statiske verdighetsscoren | ❌ | ❌ | ✅ |
|
|
445
|
+
|
|
446
|
+
\*Playwright sporer retries internt, men produserer ikke en selvstendig
|
|
447
|
+
flakiness-rapport med dommeetiketter.
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## 🤖 Hvorfor ikke bare bruke AI-kodereview?
|
|
452
|
+
|
|
453
|
+
Annet problem, annet lag. AI-review kan spotte en mistenkelig
|
|
454
|
+
testendring i en diff; det beviser ikke at verifiseringssystemet som
|
|
455
|
+
helhet er troverdig — og det ser bare diffen du viser det.
|
|
456
|
+
|
|
457
|
+
| | AI-kodereview (Copilot m.fl.) | **Mjölnir** |
|
|
458
|
+
| ------------------------------------------- | :-------------------------------------: | :--------------------------: |
|
|
459
|
+
| Kostnad per skann | Tokens (skalerer med diffstørrelsen) | **Null** (lokal, installert) |
|
|
460
|
+
| Ser hele suiten + alle CI-konfigs | Bare PR-diffen du viser | **Alt, hver gang** |
|
|
461
|
+
| Deterministisk (samme input → samme output) | ❌ (ikke-deterministisk) | **✅** |
|
|
462
|
+
| Fanger mønstre som har sovet i måneder | Bare hvis det er i konteksten | **✅** (skanner alle filer) |
|
|
463
|
+
| Husker funn mellom kjøringer | ❌ (ingen hukommelse på tvers av økter) | **✅** (baseline + diff) |
|
|
464
|
+
| Kjører uten menneskelig utløser | Krever en PR eller prompt | **✅** (CI-hook, 3 sekunder) |
|
|
465
|
+
|
|
466
|
+
**Bruk begge.** AI fanger nyanse, intensjon og designfeil ingen regex
|
|
467
|
+
kan finne. Mjölnir fanger de strukturelle mønstrene AI overser fordi de
|
|
468
|
+
ser «intensjonelle» ut — et commitet `.only`, en oppslukt exit-kode, en
|
|
469
|
+
`continue-on-error` på et testjobb. Det er ikke bugs som krever
|
|
470
|
+
resonnering; det er fakta som krever skanning.
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## 🤖 CI-integrasjon
|
|
475
|
+
|
|
476
|
+
Én kommando genererer en PR-workflow — rådgivende som standard, aldri
|
|
477
|
+
blokkerende:
|
|
478
|
+
|
|
479
|
+
```bash
|
|
480
|
+
mjolnir ci install
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Eller koble den nativt inn i GitHub Code Scanning via SARIF:
|
|
484
|
+
|
|
485
|
+
```yaml
|
|
486
|
+
- run: npx mjolnir-qa@latest --format sarif > mjolnir.sarif
|
|
487
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
488
|
+
with:
|
|
489
|
+
sarif_file: mjolnir.sarif
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
Editor- og pipeline-oppsett for SARIF:
|
|
493
|
+
[docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md).
|
|
494
|
+
|
|
495
|
+
### Changed-scope-dekning
|
|
496
|
+
|
|
497
|
+
`--scope changed` tilskriver funn de linjene branchen din la til
|
|
498
|
+
i forhold til merge-base med `main`. Den dekker testfiler (`*.spec.*`,
|
|
499
|
+
`*.test.*`) pluss GitHub-workflowfiler og Playwright-konfigurasjoner i
|
|
500
|
+
diffen. Når merge-base ikke kan resolve — shallow clone, detached HEAD,
|
|
501
|
+
ikke-git-mål, annen default-branch — degraderer den ærlig: funn faller
|
|
502
|
+
tilbake til hel-fil-attribuering, og rapporten sier det. Overskriv
|
|
503
|
+
base-ref med `--base <ref>`.
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## Konfigurasjon
|
|
508
|
+
|
|
509
|
+
Mjölnir er zero-config. En valgfri `mjolnir.config.json` (eller
|
|
510
|
+
`.mjolnir.json`) i roten av repoet fininnstiller severity, gating og
|
|
511
|
+
scope — den endrer aldri deteksjonssemantikken.
|
|
512
|
+
|
|
513
|
+
| Key | Type | Effekt |
|
|
514
|
+
| ------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
515
|
+
| `exclude` | `string[]` | Ekstra ignore-globs (gitignore-delmengde), oppå de innebygde defaultene |
|
|
516
|
+
| `gate` | `"advisory" \| "error" \| "warning"` | Hvilke severities som avslutter med ikke-null (default `error`; `advisory` blokkerer aldri) |
|
|
517
|
+
| `severityOverrides` | `{ "<RULE-ID>": severity }` | Omrangerer en regels funn for repoet ditt |
|
|
518
|
+
| `ignore` | `IgnoreEntry[]` | Undertrykker funn — **`reason` er påkrevd**; oppføringer utløper etter 90 dager (en eksplisitt `expires`-dato, eller config-filens last-modified-tid for oppføringer uten) |
|
|
519
|
+
| `plugins` | `string[]` | Regelpakker fra tredjepart (se [Tillitsmodellen](#tillitsmodellen)) |
|
|
520
|
+
|
|
521
|
+
```json
|
|
522
|
+
{
|
|
523
|
+
"gate": "error",
|
|
524
|
+
"exclude": ["legacy/**"],
|
|
525
|
+
"severityOverrides": { "QA-PW-118": "warning" },
|
|
526
|
+
"ignore": [
|
|
527
|
+
{
|
|
528
|
+
"ruleId": "QA-TEST-004",
|
|
529
|
+
"files": ["e2e/legacy-login.spec.ts"],
|
|
530
|
+
"reason": "Third-party widget needs a settle delay; tracked in JIRA-4821",
|
|
531
|
+
"expires": "2026-12-31"
|
|
532
|
+
}
|
|
533
|
+
]
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
- **`.mjolnirignore`** — en enkel gitignore-lignende fil for
|
|
538
|
+
sti-ekskluderinger, samme dialekt som `exclude`. Bruk den for
|
|
539
|
+
maskinspesifikk støy; bruk `exclude` når listen hører hjemme i
|
|
540
|
+
versjonskontroll sammen med resten av konfigurasjonen.
|
|
541
|
+
- **CLI-overrides** — `--strict` (inkluder karantèneregler),
|
|
542
|
+
`--width <cols>` og `--ascii` / `--no-ascii` (terminalrendering),
|
|
543
|
+
`--tone blunt` (hardere meldinger), `--max-duration <sec>`
|
|
544
|
+
(begrenset delvis skanning).
|
|
545
|
+
- Regelundertrykkelse og deprecation-levetid:
|
|
546
|
+
[docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md).
|
|
547
|
+
|
|
548
|
+
`ignore`-oppføringer driver også den selvstendige kommandoen
|
|
549
|
+
`mjolnir suppressions`, som lister hva som er undertrykket nå, og når
|
|
550
|
+
hver oppføring utløper.
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
## 📐 Exit-koder & kontrakter
|
|
555
|
+
|
|
556
|
+
Frosne — trygge å bygge CI-logikk på:
|
|
557
|
+
|
|
558
|
+
| Exit-kode | Betydning |
|
|
559
|
+
| --------- | --------------------------------------------------------------------------- |
|
|
560
|
+
| `0` | Rent — ingen funn på eller over gaten |
|
|
561
|
+
| `1` | Funn på eller over gaten |
|
|
562
|
+
| `2` | Delvis skanning (tidsbudsjett brukt opp, ulesebare filer) — blokkerer aldri |
|
|
563
|
+
| `10` | Bruksfeil (ugyldig flagg, manglende mål) |
|
|
564
|
+
| `20` | Intern feil |
|
|
565
|
+
|
|
566
|
+
JSON/SARIF-rapporten er `schemaVersion: 1`. Regel-IDer
|
|
567
|
+
(`QA-<FAMILY>-NNN`) er uforanderlige én gang skipet og gjenbrukes
|
|
568
|
+
aldri.
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
## Tillitsmodellen
|
|
573
|
+
|
|
574
|
+
- **Local-first** — null nettverkskall under skanning. Ever. Null
|
|
575
|
+
telemetri.
|
|
576
|
+
- **Ingen falsk bevis** — vi sier heller «ukjent» enn «verifisert». Et
|
|
577
|
+
tomt repo får `score: null`, aldri en falsk 100.
|
|
578
|
+
- **Delvis ærlighet** — hvis analysen ble avkortet, sier utdataene det.
|
|
579
|
+
Aldri «complete» når det ikke er tilfelle.
|
|
580
|
+
- **FP-brannmur** — deteksjon kjører på et kommentar-/streng-fritt view
|
|
581
|
+
av koden (TypeScript-regler bruker kompilator-AST): et mønster inne i
|
|
582
|
+
en prosakommentar eller en doc-eksempelstreng er dokumentasjon, ikke
|
|
583
|
+
et funn.
|
|
584
|
+
- **Målt, ikke påstått** — bare regler med en false-positive-rate fra
|
|
585
|
+
ekte OSS-kode skiper i overskriftstierne (se
|
|
586
|
+
[Hvor mye er målt](#hvor-mye-er-målt)); skann-fotnoten og
|
|
587
|
+
`mjolnir rules --unmeasured` forteller deg hvilke som er hva.
|
|
588
|
+
- **Plugin-tillit** — plugins er npm-pakker deklarert under
|
|
589
|
+
`"plugins"`. Det er **ingen sandbox**: plugin-kode kjører med fulle
|
|
590
|
+
Node-privilegier, samme tillitsmodell som ESLint- eller
|
|
591
|
+
Vitest-plugins. Core regel-ID-prefiks er reservert og avvises fra
|
|
592
|
+
plugins mot spoofing.
|
|
593
|
+
- **Workspace-lokale eksterne regler** (mappebaserte, null nettverk) —
|
|
594
|
+
en `mjolnir-rules/`-mappe ved siden av skannemålet loader
|
|
595
|
+
tilpassede regler: JSON-filer deklarerer regex-mønstre (ingen kode
|
|
596
|
+
eksekveres), `.mjs`/`.js`-moduler eksporterer `rules` (full
|
|
597
|
+
Node-tillit, som plugins). Eksterne regler bærer samme
|
|
598
|
+
trust-metadata som core; de kan aldri skipe i core-tieren (core
|
|
599
|
+
krever en målt FP-rate fra corpus-sidecaren — en deklarert
|
|
600
|
+
`tier: "core"` klemmes til `extended`), adlyder tier-grenser og
|
|
601
|
+
sjekkes for drift: `mjolnir rules --md --external` renderer
|
|
602
|
+
katalogen fra de lastede filene (proveniens `external`), og
|
|
603
|
+
matrisegeneratoren aksepterer `--external <root>`.
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
## 🏗️ Arkitektur
|
|
608
|
+
|
|
609
|
+
<details>
|
|
610
|
+
<summary>Utvid treet</summary>
|
|
611
|
+
|
|
612
|
+
```
|
|
613
|
+
mjolnir/
|
|
614
|
+
├── src/
|
|
615
|
+
│ ├── engine/ # LanguageAdapter interface + rule runner
|
|
616
|
+
│ ├── adapters/ # typescript · python · java · csharp · github-actions
|
|
617
|
+
│ ├── rules/ # rules across 8 families + the measured-FP table
|
|
618
|
+
│ ├── playwright/ # Selector Health Score engine
|
|
619
|
+
│ ├── discovery/ # workspace, frameworks, ignore resolution
|
|
620
|
+
│ ├── scope/ # git merge-base changed-scope engine
|
|
621
|
+
│ ├── scorer/ # transparent deduction table + prioritization
|
|
622
|
+
│ ├── reporter/ # terminal · JSON · SARIF 2.1 · Mermaid
|
|
623
|
+
│ ├── forensics/ # run-data ingestion · flake verdicts · triage
|
|
624
|
+
│ ├── config/ # mjolnir.config.json + suppressions
|
|
625
|
+
│ ├── plugins/ # third-party rule loading (no sandbox)
|
|
626
|
+
│ └── commands/ # every subcommand
|
|
627
|
+
└── tests/
|
|
628
|
+
├── fixtures/ # must-fire / must-not-fire per rule
|
|
629
|
+
└── golden/ # frozen score regression locks
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
</details>
|
|
633
|
+
|
|
634
|
+
- **Regler er rene funksjoner** — `(SourceFileContext) → Finding[]`,
|
|
635
|
+
ingen I/O, ingen globals. Nye økosystem = én adapter + dens regler.
|
|
636
|
+
- **TypeScript/Playwright bruker kompilator-AST** (ts-morph). Python,
|
|
637
|
+
Java og C# kjører på et delt regex-lag med maskerte kommentar/strenger.
|
|
638
|
+
- Et tree-sitter WASM AST-lag for Java og C# finnes og er neste
|
|
639
|
+
presisjonssteg — det er ennå ikke koblet på den synkrone
|
|
640
|
+
skanne-pipelinen.
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
## 📚 Dokumentasjon
|
|
645
|
+
|
|
646
|
+
| Dokument | Hva som er i det |
|
|
647
|
+
| ------------------------------------------------------ | ------------------------------------------- |
|
|
648
|
+
| [docs/SCORING.md](docs/SCORING.md) | Score-normalisering + bevisvektning |
|
|
649
|
+
| [docs/FP-AUDIT.md](docs/FP-AUDIT.md) | Målte false-positive-rater + metode |
|
|
650
|
+
| [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md) | Regeltilstander, undertrykking, deprecation |
|
|
651
|
+
| [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md) | SARIF-utdata + editor/CI-oppsett |
|
|
652
|
+
| [docs/rules/](docs/rules/) | Generert katalog per regel |
|
|
653
|
+
| [CONTRIBUTING.md](CONTRIBUTING.md) | Dev-oppsett + bidragsflyt |
|
|
654
|
+
| [CHANGELOG.md](CHANGELOG.md) | Utgivelseshistorikk |
|
|
655
|
+
| [SECURITY.md](SECURITY.md) | Sårbarhetsrapportering |
|
|
656
|
+
|
|
657
|
+
---
|
|
658
|
+
|
|
659
|
+
## 📈 Status
|
|
660
|
+
|
|
661
|
+
**v0.5.x · åpen beta.** JSON-skjemaet og exit-kodene er frosne
|
|
662
|
+
kontrakter. TypeScript og Python har den bredeste målte dekningen; Java
|
|
663
|
+
og C# er nyere — les dem gjennom
|
|
664
|
+
[tiers-tabellen](#regel-tiers-og-språkmodenhet).
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
## 🤝 Bidra
|
|
669
|
+
|
|
670
|
+
Nye regler er den enkleste første bidraget — én kommando scaffolder
|
|
671
|
+
regelen pluss dens must-fire- **og** must-not-fire-fixtures (den
|
|
672
|
+
genererte regelen feiler bevisst fixturene sine til du implementerer
|
|
673
|
+
ekte deteksjon — en stub kan ikke skipes):
|
|
674
|
+
|
|
675
|
+
```bash
|
|
676
|
+
mjolnir create-rule QA-PW-140 --title "Screenshot without diff bound"
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
Full dev-oppsett, standing-gate-kommandoene og anti-creep- /
|
|
680
|
+
fixture-brannmur-lovene er i [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
681
|
+
|
|
682
|
+
---
|
|
683
|
+
|
|
684
|
+
<div align="center">
|
|
685
|
+
|
|
686
|
+
**Slutt å skipe tester du ikke kan stole på.**
|
|
687
|
+
|
|
688
|
+
```bash
|
|
689
|
+
npx mjolnir-qa@latest
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
**Star ⭐ · Watch 👀 · Contribute 🤝**
|
|
693
|
+
|
|
694
|
+
Bygget av [Sergey Bar](https://www.linkedin.com/in/sergeybar/)
|
|
695
|
+
|
|
696
|
+
</div>
|