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.uk.md
ADDED
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="assets/readme/logo.png" alt="Mjölnir — Verification Trust Engine" width="800" />
|
|
4
|
+
|
|
5
|
+
### Ваші тести вам брешуть. Ми це доводимо.
|
|
6
|
+
|
|
7
|
+
**Verification Trust Engine для QA.** Mjölnir перевіряє тест-сьюти та
|
|
8
|
+
CI-пайплайни, видає показник гідності й показує точно, де ламається
|
|
9
|
+
довіра.
|
|
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](README.no.md) | [Português (Brasil)](README.br.md) | [ไทย](README.th.md) | [Türkçe](README.tr.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
|
+
**Чи заслуговують довіри ваші тести?**
|
|
25
|
+
|
|
26
|
+
[Подивіться в дії](#-подивіться-в-дії) ·
|
|
27
|
+
[Швидкий старт](#-швидкий-старт) ·
|
|
28
|
+
[Що він перевіряє](#-що-перевіряє-mjölnir) ·
|
|
29
|
+
[Скоринг](#як-працює-скор) ·
|
|
30
|
+
[CI](#-інтеграція-ci) · [Конфігурація](#конфігурація) ·
|
|
31
|
+
[Документація](#-документація)
|
|
32
|
+
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 🎬 Подивіться в дії
|
|
38
|
+
|
|
39
|
+
<p align="center">
|
|
40
|
+
<img src="assets/readme/demo.svg" alt="Повний --verbose-звіт Mjölnir по демо-репозиторію: WORTHINESS 75/100 NEEDS WORK, розбивка діагностик за категоріями, список FIX THIS FIRST і кожна знахідка з ID правила та номером рядка — CI, Playwright, тест-гігієна і Python-правила" width="900" />
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<sub>Повний вивід `npx mjolnir-qa ./examples/demo-repo --verbose`,
|
|
44
|
+
відтворений справжнім репортером — нічого не обрізано. Перегенеровується
|
|
45
|
+
командою `npm run docs:demo`;
|
|
46
|
+
[`tests/demo-asset-reproducibility.spec.ts`](tests/demo-asset-reproducibility.spec.ts)
|
|
47
|
+
валить CI, якщо артефакт розійшовся з тим, що друкує інструмент.</sub>
|
|
48
|
+
|
|
49
|
+
**Що щойно сталося:**
|
|
50
|
+
|
|
51
|
+
1. Mjölnir знайшов Playwright-спеки, свою конфігурацію, CI-workflow і
|
|
52
|
+
Python-тестовий файл — чотири мови/формати за один прохід.
|
|
53
|
+
2. Він знайшов докази, що послаблюють довіру до сьюта —
|
|
54
|
+
`continue-on-error`, що маскує job, `|| true`, що ковтає exit-код,
|
|
55
|
+
жорсткі sleep'и, крихкий селектор, захардкоджені staging-URL,
|
|
56
|
+
очікування `networkidle`.
|
|
57
|
+
3. Кожну він перетворив на конкретну знахідку з ID правила, місцем і
|
|
58
|
+
фіксом — і в єдиний скор, за яким можна гейтити PR.
|
|
59
|
+
|
|
60
|
+
### Одна знахідка зблизька
|
|
61
|
+
|
|
62
|
+
Запустіть `mjolnir explain QA-CI-001` на першій знахідці вище — і
|
|
63
|
+
отримаєте:
|
|
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
|
+
Ось одиниця цінності: не дрібниця стилю, а місце, де ваш CI каже, що
|
|
85
|
+
щось пройшло, хоча воно не пройшло.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## ⚡ Швидкий старт
|
|
90
|
+
|
|
91
|
+
Запустіть на репозиторії — отримаєте повний звіт і показник гідності:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npx mjolnir-qa@latest
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**У CI продукт — одна команда.** Вона сканує лише зачеплене гілкою та
|
|
98
|
+
завершується з ненульовим кодом за нових проблем:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npx mjolnir-qa@latest --scope changed
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Вбудуйте це у PR-check — `mjolnir ci install` пише workflow — і готово.
|
|
105
|
+
Все інше опціональне.
|
|
106
|
+
|
|
107
|
+
| Команда | Що робить |
|
|
108
|
+
| ----------------------------------- | --------------------------------------------------------- |
|
|
109
|
+
| `mjolnir` | Скан усього репо + показник гідності |
|
|
110
|
+
| `mjolnir --scope changed` | Лише те, що принесла ваша гілка — CI-режим |
|
|
111
|
+
| `mjolnir ci install` | Генерує рекомендаційний PR-workflow |
|
|
112
|
+
| `mjolnir explain QA-CI-001` | Що / чому / фікс + виміряний FP-рейт для правила |
|
|
113
|
+
| `mjolnir rules --unmeasured` | Правила, що працюють за припущенням, а не за вимірюванням |
|
|
114
|
+
| `mjolnir --json` / `--format sarif` | Машинночитабельно / GitHub Code Scanning |
|
|
115
|
+
| `mjolnir --strict` | Також правила tier-у quarantine (вищий ризик FP) |
|
|
116
|
+
|
|
117
|
+
<details>
|
|
118
|
+
<summary><strong>Коли щось флає</strong></summary>
|
|
119
|
+
|
|
120
|
+
| Команда | Що робить |
|
|
121
|
+
| ----------------------------------- | ------------------------------------------------------------- |
|
|
122
|
+
| `mjolnir forensics ./test-results/` | Реальні дані прогонів → вердикти `TRUE-FLAKE`, `FLAKY.md` |
|
|
123
|
+
| `mjolnir triage ./test-results/` | Пропозиція карантину з історії виконання |
|
|
124
|
+
| `mjolnir pw-report ./test-results/` | Зведення прогону Playwright — ретраї / флейки / найповільніші |
|
|
125
|
+
| `mjolnir doctor:playwright` | Глибокий скан лише Playwright + Selector Health Score |
|
|
126
|
+
|
|
127
|
+
</details>
|
|
128
|
+
|
|
129
|
+
<details>
|
|
130
|
+
<summary><strong>Зрідка / звіти</strong></summary>
|
|
131
|
+
|
|
132
|
+
| Команда | Що робить |
|
|
133
|
+
| ------------------------------- | ------------------------------------------------- |
|
|
134
|
+
| `mjolnir fix --dry-run` / `fix` | Безпечні автофікси з доказом |
|
|
135
|
+
| `mjolnir baseline` / `diff` | Знімок знахідок, далі звіт лише нових/погіршених |
|
|
136
|
+
| `mjolnir impact --since <ref>` | Що змінилося з моменту ранішого коміту |
|
|
137
|
+
| `mjolnir debt` | Реєстр тестового боргу з моделлю вартості |
|
|
138
|
+
| `mjolnir handover` | Карта онбордингу сьюта для нового QA |
|
|
139
|
+
| `mjolnir stats` | Локальні накопичені лічильники побачених фіксів |
|
|
140
|
+
| `mjolnir badge` | JSON shields.io-ендпоінту + сніпет |
|
|
141
|
+
| `mjolnir rules --md` | Повний каталог правил (JSON або Markdown) |
|
|
142
|
+
| `mjolnir doctor` | Самоаудит власної бази правил Mjölnir |
|
|
143
|
+
| `mjolnir create-rule <ID>` | Скаффолд нового правила + фікстур |
|
|
144
|
+
| `mjolnir --format mermaid` | Діаграма тестової архітектури для коментаря до PR |
|
|
145
|
+
|
|
146
|
+
</details>
|
|
147
|
+
|
|
148
|
+
Установіть глобально замість `npx`, якщо так зручніше:
|
|
149
|
+
`npm i -g mjolnir-qa`. Потребує Node.js ≥ 22.18. Працює на Windows,
|
|
150
|
+
macOS і Linux.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 👥 Для кого це?
|
|
155
|
+
|
|
156
|
+
- **QA / SDET**, які володіють e2e- чи інтеграційним с'ютом і яким
|
|
157
|
+
потрібні докази, що с'ют справді заслуговує зелену галочку, яку він
|
|
158
|
+
видає.
|
|
159
|
+
- **Платформові / DevEx-команди**, відповідальні за цілісність CI та
|
|
160
|
+
release-gates — ті, для кого `continue-on-error` ніколи не повинен
|
|
161
|
+
мовчки перефарбовувати червоний пайплайн у зелений.
|
|
162
|
+
- **OSS-мейнтейнери**, яким потрібен дешевий, завжди увімкнений
|
|
163
|
+
верифікаційний гейт, що працює локально й у CI без мережевих
|
|
164
|
+
викликів.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 🔨 Що перевіряє Mjölnir
|
|
169
|
+
|
|
170
|
+
| | |
|
|
171
|
+
| --- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
172
|
+
| ⚖️ | **Показник гідності** — одне число, прозора таблиця відрахувань, жодного чорного ящика |
|
|
173
|
+
| 🎭 | **Selector Health Score** — оцінює ваші Playwright-локатори, а не лише pass-rate |
|
|
174
|
+
| 🔬 | **Runtime-криміналістика** — читає реальні дані прогонів Playwright/JUnit і ловить `TRUE-FLAKE`, а не лише статичні здогадки |
|
|
175
|
+
| 🚨 | **Правила цілісності CI** — ловить `continue-on-error`, `\|\| true` та інші трюки з хибним зеленим |
|
|
176
|
+
| 🐍 | **Усі чотири Playwright-бінінги** — TypeScript, Python, Java, C#/.NET — плюс pytest, JUnit/TestNG і CI-workflows |
|
|
177
|
+
| 🔒 | **Local-first** — нуль мережевих викликів під час сканування, нуль телеметрії, робота за секунди |
|
|
178
|
+
|
|
179
|
+
### Правила
|
|
180
|
+
|
|
181
|
+
Кожне правило постачається з must-fire- **та** must-not-fire-фікстурами.
|
|
182
|
+
Правило, що спрацьовує на власній негативній фікстурі, не може вийти —
|
|
183
|
+
це фаєрвол хибних спрацювань.
|
|
184
|
+
|
|
185
|
+
<details>
|
|
186
|
+
<summary><strong>Тест-гігієна</strong></summary>
|
|
187
|
+
|
|
188
|
+
| ID | Правило | Severity |
|
|
189
|
+
| ----------- | ------------------------------------------------------- | -------- |
|
|
190
|
+
| QA-TEST-001 | Закомічено сфокусований тест (`.only`, `fit`) | error |
|
|
191
|
+
| QA-TEST-002 | Пропущено тест без обґрунтування | error |
|
|
192
|
+
| QA-TEST-002 | Пропущено тест з облікованим обґрунтуванням | warning |
|
|
193
|
+
| QA-TEST-003 | Тест без ассертів | error |
|
|
194
|
+
| QA-TEST-004 | Жорсткий sleep (`waitForTimeout`, `sleep()`, `delay()`) | warning |
|
|
195
|
+
| QA-TEST-006 | Зловживання ретраями, що приховує флейкість | warning |
|
|
196
|
+
| QA-TEST-010 | Порожнє тіло тесту | error |
|
|
197
|
+
|
|
198
|
+
</details>
|
|
199
|
+
|
|
200
|
+
<details>
|
|
201
|
+
<summary><strong>Якість тестів</strong></summary>
|
|
202
|
+
|
|
203
|
+
| ID | Правило | Severity |
|
|
204
|
+
| ------------ | ----------------------------- | -------- |
|
|
205
|
+
| QA-TQUAL-001 | Верифікація лише моками | info |
|
|
206
|
+
| QA-TQUAL-002 | Тавтологічний ассерт | error |
|
|
207
|
+
| QA-TQUAL-009 | Ассерт не-awaitнутого promise | error |
|
|
208
|
+
| QA-TQUAL-011 | Закоментовані тести | warning |
|
|
209
|
+
|
|
210
|
+
</details>
|
|
211
|
+
|
|
212
|
+
<details>
|
|
213
|
+
<summary><strong>Playwright 🎭</strong></summary>
|
|
214
|
+
|
|
215
|
+
| ID | Правило | Severity |
|
|
216
|
+
| --------- | --------------------------------------------- | -------- |
|
|
217
|
+
| QA-PW-002 | Ассерт локатора без await | error |
|
|
218
|
+
| QA-PW-003 | `page.pause()` / `test.only()` у коміті | error |
|
|
219
|
+
| QA-PW-004 | Крихкі CSS/XPath-селектори | warning |
|
|
220
|
+
| QA-PW-005 | Бізнес-логіка всередині `page.evaluate()` | info |
|
|
221
|
+
| QA-PW-114 | Легасі element handles (`page.$`) | info |
|
|
222
|
+
| QA-PW-118 | Очікування `networkidle` (флейкові by design) | info |
|
|
223
|
+
| QA-PW-123 | Захардкоджені URL середовищ | warning |
|
|
224
|
+
|
|
225
|
+
</details>
|
|
226
|
+
|
|
227
|
+
<details>
|
|
228
|
+
<summary><strong>Цілісність CI</strong></summary>
|
|
229
|
+
|
|
230
|
+
| ID | Правило | Severity |
|
|
231
|
+
| --------- | ---------------------------------------------------------------- | -------- |
|
|
232
|
+
| QA-CI-001 | `continue-on-error` маскує падіння | error |
|
|
233
|
+
| QA-CI-002 | `\|\| true` ковтає exit-коди | error |
|
|
234
|
+
| QA-CI-005 | Звіт споживається, але ніколи не генерується | error |
|
|
235
|
+
| QA-CI-007 | Retry-обгортки навколо тестів | warning |
|
|
236
|
+
| QA-CI-008 | Завжди успішний крок маскує падіння | error |
|
|
237
|
+
| QA-CI-009 | Exit-код тесту не прокидається (`\|` без pipefail, ланцюжки `;`) | error |
|
|
238
|
+
| QA-CI-010 | Тести пропускаються там, де мають блокувати (skip-on-PR-гарди) | error |
|
|
239
|
+
|
|
240
|
+
</details>
|
|
241
|
+
|
|
242
|
+
<details>
|
|
243
|
+
<summary><strong>Python / pytest 🐍</strong></summary>
|
|
244
|
+
|
|
245
|
+
| ID | Правило | Severity |
|
|
246
|
+
| --------- | ------------------------------------------- | -------- |
|
|
247
|
+
| QA-PY-002 | Пропущений тест (`skip`, нестрогий `xfail`) | warning |
|
|
248
|
+
| QA-PY-003 | Тестова функція без ассертів | error |
|
|
249
|
+
| QA-PY-005 | `time.sleep()` у тестах | warning |
|
|
250
|
+
| QA-PY-006 | Порожнє тіло тесту (`pass`) | info |
|
|
251
|
+
| QA-PY-010 | Залежність від випадковості/часу без freeze | info |
|
|
252
|
+
| QA-PY-012 | Тавтологічний ассерт | error |
|
|
253
|
+
|
|
254
|
+
Усього 20 Python-правил (QA-PY-001…012 гігієна pytest + QA-PY-101…108 Playwright-Python).
|
|
255
|
+
|
|
256
|
+
</details>
|
|
257
|
+
|
|
258
|
+
<details>
|
|
259
|
+
<summary><strong>Java / JUnit · TestNG ☕</strong></summary>
|
|
260
|
+
|
|
261
|
+
| ID | Правило | Severity |
|
|
262
|
+
| --------- | -------------------------------------------- | -------- |
|
|
263
|
+
| QA-JV-101 | Вимкнений тест (`@Disabled`) | warning |
|
|
264
|
+
| QA-JV-102 | Жорсткий sleep (`Thread.sleep()`) | warning |
|
|
265
|
+
| QA-JV-103 | Тестовий метод без ассертів | error |
|
|
266
|
+
| QA-JV-105 | Жорсткий sleep Playwright `waitForTimeout()` | warning |
|
|
267
|
+
| QA-JV-106 | Крихкий селектор замість role-локатора | warning |
|
|
268
|
+
| QA-JV-108 | Захардкоджений URL середовища у тесті | info |
|
|
269
|
+
| QA-JV-111 | Бланкетний мок `page.route("**")` | info |
|
|
270
|
+
|
|
271
|
+
</details>
|
|
272
|
+
|
|
273
|
+
<details>
|
|
274
|
+
<summary><strong>C# / .NET — NUnit · xUnit · MSTest 🟣</strong></summary>
|
|
275
|
+
|
|
276
|
+
| ID | Правило | Severity |
|
|
277
|
+
| --------- | ---------------------------------------------- | -------- |
|
|
278
|
+
| QA-CS-101 | Пропущений тест (`[Ignore]`, `[Fact(Skip=)]`) | warning |
|
|
279
|
+
| QA-CS-102 | Жорсткий sleep (`Thread.Sleep` / `Task.Delay`) | warning |
|
|
280
|
+
| QA-CS-103 | Тестовий метод без ассертів | error |
|
|
281
|
+
| QA-CS-105 | Жорсткий sleep `WaitForTimeoutAsync()` | warning |
|
|
282
|
+
| QA-CS-106 | Крихкий селектор замість role-локатора | warning |
|
|
283
|
+
| QA-CS-108 | Захардкоджений URL середовища у тесті | info |
|
|
284
|
+
| QA-CS-111 | Бланкетний мок `page.RouteAsync("**")` | info |
|
|
285
|
+
|
|
286
|
+
</details>
|
|
287
|
+
|
|
288
|
+
> Повний живий каталог — кожне правило з tier, confidence, ризиком
|
|
289
|
+
> хибних спрацювань і доступністю автофікса — генерується з реєстру:
|
|
290
|
+
>
|
|
291
|
+
> ```bash
|
|
292
|
+
> mjolnir rules --md
|
|
293
|
+
> ```
|
|
294
|
+
>
|
|
295
|
+
> Сторінки за правилами лежать у [`docs/rules/`](docs/rules/).
|
|
296
|
+
|
|
297
|
+
### Скільки з цього виміряно
|
|
298
|
+
|
|
299
|
+
**74 з 99 правил несуть хибнопозитивну частоту, виміряну на реальному
|
|
300
|
+
OSS-коді** (по ≥ 10 вручну класифікованих знахідок на правило; див.
|
|
301
|
+
[docs/FP-AUDIT.md](docs/FP-AUDIT.md)). Інші 19 виходять на оцінці
|
|
302
|
+
автора. Футер кожного скана каже, скільки із _спрацьованих_ правил
|
|
303
|
+
виміряно; `mjolnir rules --unmeasured` перелічує невиміряні; сторінка
|
|
304
|
+
`mjolnir explain` кожного правила вказує її статус. Ми публікуємо
|
|
305
|
+
частоту, навіть коли вона негарна — QA-CS-103 аудитується на 95 % і за
|
|
306
|
+
це відправлено в карантин. Збільшувати ці 78 — постійна робота проєкту.
|
|
307
|
+
|
|
308
|
+
### Тіри правил і зрілість мов
|
|
309
|
+
|
|
310
|
+
Кожне правило — `core`, `extended` чи `quarantine`, призначене за його
|
|
311
|
+
**виміряною** частотою хибних спрацювань:
|
|
312
|
+
|
|
313
|
+
| Tier | Значення | Скан за замовчуванням | `--strict` |
|
|
314
|
+
| ------------ | -------------------------------------- | :-------------------: | :--------: |
|
|
315
|
+
| `core` | ≤ 10 % виміряних FP | ✅ | ✅ |
|
|
316
|
+
| `extended` | ≤ 30 % виміряних FP | ✅ | ✅ |
|
|
317
|
+
| `quarantine` | понад 30 % або ще не виміряно (n < 10) | ❌ | ✅ |
|
|
318
|
+
|
|
319
|
+
| Мова | Адаптер | Охват сьогодні |
|
|
320
|
+
| --------------- | --------------- | ----------------------------------------------------------- |
|
|
321
|
+
| TypeScript / JS | AST компілятора | найширший, найбільш виміряний — переважно `core`/`extended` |
|
|
322
|
+
| Python / pytest | Regex-шар | широкий, перевірений корпусом — переважно `core`/`extended` |
|
|
323
|
+
| Java | Regex-шар | новіший — переважно `extended`/`quarantine` |
|
|
324
|
+
| C# / .NET | Regex-шар | новіший — переважно `extended`/`quarantine` |
|
|
325
|
+
|
|
326
|
+
У TypeScript і Python найширший виміряний охват. Java і C# вийшли,
|
|
327
|
+
задокументовані й залишаються за межами головного числа, доки реальний
|
|
328
|
+
с'ют-споживач (не власні тести біндинг-бібліотеки) не буде проаудовано.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Як працює скор
|
|
333
|
+
|
|
334
|
+
<p align="center">
|
|
335
|
+
<img src="assets/readme/terminal-hero.svg" alt="Термінальний вивід Mjölnir — WORTHINESS 75/100 NEEDS WORK, розбивка діагностик за категоріями і список FIX THIS FIRST" width="820" />
|
|
336
|
+
</p>
|
|
337
|
+
|
|
338
|
+
<sub>Перегенеровується командою `npm run docs:hero`;
|
|
339
|
+
[`tests/hero-asset-reproducibility.spec.ts`](tests/hero-asset-reproducibility.spec.ts)
|
|
340
|
+
валить CI, якщо артефакт розійшовся з тим, що друкує репортер.</sub>
|
|
341
|
+
|
|
342
|
+
Скор прозорий: **error −8, warning −3, info −1**, далі нормування на
|
|
343
|
+
експозицію с'юта (відрахування на оголошення тесту). Відрахування,
|
|
344
|
+
зважені за доказами, означають, що слабкі сигнали коштують дешевше.
|
|
345
|
+
Термінал показує ті самі зі скидкою числа, що використовує скор —
|
|
346
|
+
жодного чорного ящика. Повна методика: [docs/SCORING.md](docs/SCORING.md).
|
|
347
|
+
|
|
348
|
+
**Вердикти**
|
|
349
|
+
|
|
350
|
+
| Score | Вердикт |
|
|
351
|
+
| ------- | ---------------- |
|
|
352
|
+
| ≥ 80 | ✓ **WORTHY** |
|
|
353
|
+
| 50 – 79 | ⚠ **NEEDS WORK** |
|
|
354
|
+
| < 50 | ✖ **UNWORTHY** |
|
|
355
|
+
|
|
356
|
+
**Рівні доказів** — кожна знахідка несе один; він задає вагу знахідки в
|
|
357
|
+
скорі:
|
|
358
|
+
|
|
359
|
+
| Рівень | Значення | Вплив на скор | Приклад |
|
|
360
|
+
| ------ | --------------------- | ---------------------- | ------------------------------------------------------- |
|
|
361
|
+
| E2 | Детермінований дефект | Повне відрахування | `.only` у коміті — структурно доказово |
|
|
362
|
+
| E1 | Евристичний патерн | Половинне відрахування | Знайдений regex'ом `sleep()` — сильний сигнал, не доказ |
|
|
363
|
+
| E0 | Спостереження | Нуль (тільки info) | Репортиться, але ніколи не гейтить CI і не віднімає |
|
|
364
|
+
|
|
365
|
+
Більшість правил — **E1**. Слоган «we prove it» відсилає до цієї
|
|
366
|
+
системи: знахідки E2 — структурне доказ; знахідки E1 — коректно
|
|
367
|
+
позиційовані попередження, не формальні докази.
|
|
368
|
+
|
|
369
|
+
Порожній репозиторій отримує `null`, ніколи фейкову сотню — див.
|
|
370
|
+
[Модель довіри](#модель-довіри).
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## 🎭 Selector Health Score
|
|
375
|
+
|
|
376
|
+
Головна метрика для Playwright-с'ютів — наскільки стійкі ваші локатори:
|
|
377
|
+
|
|
378
|
+
```text
|
|
379
|
+
▚▞ SELECTOR HEALTH — e2e/checkout.spec.ts
|
|
380
|
+
|
|
381
|
+
[█████████████████░░░] 83 / 100
|
|
382
|
+
role/text: 2 · testid: 1 · css-chains: 1 ⚠ · xpath: 0
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Локатори на основі ролей отримують повний бал. Ланцюжки CSS-класів і
|
|
386
|
+
XPath топлять скор — вони ламаються на будь-якому DOM-рефакторі, не
|
|
387
|
+
повідомляючи, яку поведінку регресовано.
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
## 🔬 Runtime-докази
|
|
392
|
+
|
|
393
|
+
Статичне виявлення флейкості — гадання. Mjölnir читає **реальні дані
|
|
394
|
+
виконання** — JSON-звіти Playwright та XML JUnit від будь-якого
|
|
395
|
+
раннера:
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
mjolnir forensics ./test-results/
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
```text
|
|
402
|
+
▚▞ FLAKINESS LEADERBOARD
|
|
403
|
+
|
|
404
|
+
3 tests · 1 failed · 1 flaky · 1 retried
|
|
405
|
+
|
|
406
|
+
TRUE-FLAKE completes checkout with saved card (e2e/checkout.spec.ts)
|
|
407
|
+
████████████████████ 6.0s · 2 attempts
|
|
408
|
+
FAILING declines an expired card (e2e/checkout.spec.ts)
|
|
409
|
+
████░░░░░░░░░░░░░░░░ 1.1s · 1 attempt
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Тест, що проходить лише з спроби ≥ 2, — не прохідний тест; це везучий
|
|
413
|
+
тест. Він позначається `TRUE-FLAKE` незалежно від фінальної зеленої
|
|
414
|
+
галочки.
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## ⚡ Mjölnir — не ще один лінтер
|
|
419
|
+
|
|
420
|
+
Лінтери кажуть, чи відповідає код правилам. Mjölnir каже, чи можна
|
|
421
|
+
довіряти вашій верифікації.
|
|
422
|
+
|
|
423
|
+
| | ESLint / SonarQube | Coverage-інструменти | Ручне рев'ю | **Mjölnir** |
|
|
424
|
+
| --------------------------------------------------------- | :----------------: | :------------------: | :---------: | :---------: |
|
|
425
|
+
| Цілісність CI-workflow (`continue-on-error`, `\|\| true`) | ❌ | ❌ | рідко | ✅ |
|
|
426
|
+
| Крос-мова (TS, Python, Java, C#) з одного інструменту | ❌ | ❌ | ❌ | ✅ |
|
|
427
|
+
| Оцінює стійкість Playwright-локаторів (Selector Health) | ❌ | ❌ | рідко | ✅ |
|
|
428
|
+
| Позначає тести без справжніх ассертів | ✅ (плагін)\* | ❌ | іноді | ✅ |
|
|
429
|
+
| Ловить жорсткі sleep'и (`waitForTimeout`, `time.sleep`) | ✅ (плагін)\* | ❌ | іноді | ✅ |
|
|
430
|
+
| Працює за секунди, нуль мережевих викликів під час скана | ✅ | ✅ | — | ✅ |
|
|
431
|
+
|
|
432
|
+
\*`eslint-plugin-jest` (`expect-expect`) і `eslint-plugin-playwright`
|
|
433
|
+
(`expect-expect`, `no-wait-for-timeout`) покривають це для своїх
|
|
434
|
+
фреймворків.
|
|
435
|
+
|
|
436
|
+
**Runtime-аналіз** — окрема категорія поруч зі статичним лінтингом:
|
|
437
|
+
|
|
438
|
+
| | Playwright retry reporter | Allure / ReportPortal | **Mjölnir forensics** |
|
|
439
|
+
| ------------------------------------------------------ | :-----------------------: | :-------------------: | :-------------------: |
|
|
440
|
+
| Читає реальні дані прогонів для вердиктів `TRUE-FLAKE` | частково\* | частково (тег) | ✅ |
|
|
441
|
+
| Звіт флейк-тріажу з історії виконання | ❌ | ✅ | ✅ |
|
|
442
|
+
| Інтегрується зі статичним скором гідності | ❌ | ❌ | ✅ |
|
|
443
|
+
|
|
444
|
+
\*Playwright відстежує ретраї всередині, але не видає самостійного
|
|
445
|
+
звіту про флейкість з вердиктними мітками.
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## 🤖 Чому б не використати просто AI-код-рев'ю?
|
|
450
|
+
|
|
451
|
+
Інша проблема, інший шар. AI-рев'ю може помітити підозрілу зміну тесту
|
|
452
|
+
в дифі; воно не доводить, що система верифікації в цілому заслуговує
|
|
453
|
+
довіри — і бачить лише показаний йому диф.
|
|
454
|
+
|
|
455
|
+
| | AI-код-рев'ю (Copilot тощо) | **Mjölnir** |
|
|
456
|
+
| ------------------------------------------------- | :------------------------------: | :-------------------------------: |
|
|
457
|
+
| Ціна за скан | Токени (ростуть з розміром дифа) | **Нуль** (локально, встановлений) |
|
|
458
|
+
| Бачить увесь с'ют + усі CI-конфіги | Лише PR-диф, показаний йому | **Все, щоразу** |
|
|
459
|
+
| Детермінований (той самий вхід → той самий вихід) | ❌ (недетермінований) | **✅** |
|
|
460
|
+
| Ловить патерни, що дрімають місяцями | Лише якщо в контексті | **✅** (сканує всі файли) |
|
|
461
|
+
| Пам'ятає знахідки між запусками | ❌ (немає пам'яті між сесіями) | **✅** (baseline + diff) |
|
|
462
|
+
| Запускається без людини | Потрібен PR чи промпт | **✅** (CI-хук, 3 секунди) |
|
|
463
|
+
|
|
464
|
+
**Використовуйте обидва.** AI ловить нюанс, задум і дизайнерські вади,
|
|
465
|
+
яких не знайде жоден regex. Mjölnir ловить структурні патерни, які AI
|
|
466
|
+
упускає, бо ті виглядають «наміреними» — закомічений `.only`,
|
|
467
|
+
проковтнутий exit-код, `continue-on-error` на тестовому job. Це не
|
|
468
|
+
баги, що потребують міркувань; це факти, що потребують сканування.
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## 🤖 Інтеграція CI
|
|
473
|
+
|
|
474
|
+
Одна команда генерує PR-workflow — за замовчуванням рекомендаційний,
|
|
475
|
+
ніколи блокувальний:
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
mjolnir ci install
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Або підключіть нативно до GitHub Code Scanning через SARIF:
|
|
482
|
+
|
|
483
|
+
```yaml
|
|
484
|
+
- run: npx mjolnir-qa@latest --format sarif > mjolnir.sarif
|
|
485
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
486
|
+
with:
|
|
487
|
+
sarif_file: mjolnir.sarif
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
Налаштування редактора і пайплайну для SARIF:
|
|
491
|
+
[docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md).
|
|
492
|
+
|
|
493
|
+
### Охват за зміненим scope
|
|
494
|
+
|
|
495
|
+
`--scope changed` атрибуцію знахідок рядкам, доданим у вашій гілці
|
|
496
|
+
відносно merge-base з `main`. Він покриває тестові файли
|
|
497
|
+
(`*.spec.*`, `*.test.*`), плюс workflow-файли GitHub і конфігурації
|
|
498
|
+
Playwright у дифі. Коли merge-base не розв'язується — shallow clone,
|
|
499
|
+
detached HEAD, не-git-ціль, інша дефолтна гілка — він чесно деградує:
|
|
500
|
+
знахідки повертаються до атрибуції на весь файл, і звіт про це каже.
|
|
501
|
+
Перевизначте базову ref через `--base <ref>`.
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
## Конфігурація
|
|
506
|
+
|
|
507
|
+
Mjölnir — zero-config. Опціональний `mjolnir.config.json` (або
|
|
508
|
+
`.mjolnir.json`) у корені репо підлаштовує severity, гейтинг і scope —
|
|
509
|
+
він ніколи не змінює семантику детекції.
|
|
510
|
+
|
|
511
|
+
| Key | Тип | Дія |
|
|
512
|
+
| ------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
513
|
+
| `exclude` | `string[]` | Додаткові ignore-глоби (підмножина gitignore), поверх вбудованих дефолтів |
|
|
514
|
+
| `gate` | `"advisory" \| "error" \| "warning"` | Які severity завершують процес ненульовим кодом (за замовчуванням `error`; `advisory` ніколи не блокує) |
|
|
515
|
+
| `severityOverrides` | `{ "<RULE-ID>": severity }` | Переранговує знахідки правила для вашого репо |
|
|
516
|
+
| `ignore` | `IgnoreEntry[]` | Пригнічує знахідки — **`reason` обов'язковий**; записи спливають через 90 днів (явна дата `expires`, або час останньої зміни файлу конфіга для записів без неї) |
|
|
517
|
+
| `plugins` | `string[]` | Сторонні пакети правил (див. [Модель довіри](#модель-довіри)) |
|
|
518
|
+
|
|
519
|
+
```json
|
|
520
|
+
{
|
|
521
|
+
"gate": "error",
|
|
522
|
+
"exclude": ["legacy/**"],
|
|
523
|
+
"severityOverrides": { "QA-PW-118": "warning" },
|
|
524
|
+
"ignore": [
|
|
525
|
+
{
|
|
526
|
+
"ruleId": "QA-TEST-004",
|
|
527
|
+
"files": ["e2e/legacy-login.spec.ts"],
|
|
528
|
+
"reason": "Third-party widget needs a settle delay; tracked in JIRA-4821",
|
|
529
|
+
"expires": "2026-12-31"
|
|
530
|
+
}
|
|
531
|
+
]
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
- **`.mjolnirignore`** — простий файл у стилі gitignore для винятків
|
|
536
|
+
шляхів, той самий діалект, що `exclude`. Використовуйте його для
|
|
537
|
+
машинного шуму; використовуйте `exclude`, коли список має жити у
|
|
538
|
+
версійному контролі разом з іншою конфігурацією.
|
|
539
|
+
- **CLI-перевизначення** — `--strict` (увімкнути правила карантину),
|
|
540
|
+
`--width <cols>` і `--ascii` / `--no-ascii` (термінальний рендер),
|
|
541
|
+
`--tone blunt` (різкіші повідомлення), `--max-duration <sec>`
|
|
542
|
+
(обмежений частковий скан).
|
|
543
|
+
- Пригнічення правил і життєвий цикл депрекації:
|
|
544
|
+
[docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md).
|
|
545
|
+
|
|
546
|
+
Записи `ignore` також живлять окрему команду `mjolnir suppressions`,
|
|
547
|
+
яка перелічує поточні пригнічення та час спливання кожного запису.
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
551
|
+
## 📐 Коди виходу та контракти
|
|
552
|
+
|
|
553
|
+
Заморожені — безпечно будувати на них CI-логіку:
|
|
554
|
+
|
|
555
|
+
| Код виходу | Значення |
|
|
556
|
+
| ---------- | ----------------------------------------------------------------------------- |
|
|
557
|
+
| `0` | Чисто — немає знахідок на рівні гейта або вище |
|
|
558
|
+
| `1` | Знахідки на рівні гейта або вище |
|
|
559
|
+
| `2` | Частковий скан (вичерпано бюджет часу, нечитабельні файли) — ніколи не блокує |
|
|
560
|
+
| `10` | Помилка використання (поганий прапор, відсутня ціль) |
|
|
561
|
+
| `20` | Внутрішня помилка |
|
|
562
|
+
|
|
563
|
+
JSON/SARIF-звіт — `schemaVersion: 1`. ID правил (`QA-<FAMILY>-NNN`)
|
|
564
|
+
незмінні після виходу і ніколи не використовуються повторно.
|
|
565
|
+
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
## Модель довіри
|
|
569
|
+
|
|
570
|
+
- **Local-first** — нуль мережевих викликів під час сканування. Ніколи.
|
|
571
|
+
Нуль телеметрії.
|
|
572
|
+
- **Жодних хибних доказів** — ми радше скажемо «невідомо», ніж
|
|
573
|
+
«перевірено». Порожнє репо отримує `score: null`, ніколи фейкову
|
|
574
|
+
сотню.
|
|
575
|
+
- **Часткова чесність** — якщо аналіз обірвано, вивід про це каже.
|
|
576
|
+
Ніколи «complete», коли це не так.
|
|
577
|
+
- **FP-фаєрвол** — детекція працює на очищеному від коментарів і рядків
|
|
578
|
+
поданні коду (правила TypeScript використовують AST компілятора):
|
|
579
|
+
патерн усередині прозаїчного коментаря чи док-прикладу-рядка — це
|
|
580
|
+
документація, а не знахідка.
|
|
581
|
+
- **Виміряно, а не заявлено** — у головні тіри виходять лише правила з
|
|
582
|
+
частотою хибних спрацювань з реального OSS-коду (див.
|
|
583
|
+
[Скільки з цього виміряно](#скільки-з-цього-виміряно)); футер скана і
|
|
584
|
+
`mjolnir rules --unmeasured` скажуть, які які.
|
|
585
|
+
- **Довіра до плагінів** — плагіни — це npm-пакети, оголошені у
|
|
586
|
+
`"plugins"`. **Пісочниці немає**: код плагіна працює з повними
|
|
587
|
+
привілеями Node, та сама модель довіри, що в плагінів ESLint чи
|
|
588
|
+
Vitest. Префікси ID основних правил зарезервовані й відкидаються від
|
|
589
|
+
плагінів проти підміни.
|
|
590
|
+
- **Workspace-локальні зовнішні правила** (фолдерні, нуль мережі) —
|
|
591
|
+
каталог `mjolnir-rules/` поруч із ціллю скана завантажує власні
|
|
592
|
+
правила: JSON-файли декларують regex-патерни (код не виконується),
|
|
593
|
+
модулі `.mjs`/`.js` експортують `rules` (повна довіра Node, як у
|
|
594
|
+
плагінів). Зовнішні правила несуть ті самі trust-метадані, що й
|
|
595
|
+
core; вони ніколи не можуть вийти в core-тирі (core вимагає
|
|
596
|
+
виміряної FP-частоти з corpus-сайдкара — заявлений `tier: "core"`
|
|
597
|
+
затискається до `extended`), дотримуються тирових лімітів і
|
|
598
|
+
перевіряються на дрейф: `mjolnir rules --md --external` рендерить
|
|
599
|
+
каталог із завантажених файлів (походження `external`), а генератор
|
|
600
|
+
матриці приймає `--external <root>`.
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
## 🏗️ Архітектура
|
|
605
|
+
|
|
606
|
+
<details>
|
|
607
|
+
<summary>Розгорнути дерево</summary>
|
|
608
|
+
|
|
609
|
+
```
|
|
610
|
+
mjolnir/
|
|
611
|
+
├── src/
|
|
612
|
+
│ ├── engine/ # LanguageAdapter interface + rule runner
|
|
613
|
+
│ ├── adapters/ # typescript · python · java · csharp · github-actions
|
|
614
|
+
│ ├── rules/ # rules across 8 families + the measured-FP table
|
|
615
|
+
│ ├── playwright/ # Selector Health Score engine
|
|
616
|
+
│ ├── discovery/ # workspace, frameworks, ignore resolution
|
|
617
|
+
│ ├── scope/ # git merge-base changed-scope engine
|
|
618
|
+
│ ├── scorer/ # transparent deduction table + prioritization
|
|
619
|
+
│ ├── reporter/ # terminal · JSON · SARIF 2.1 · Mermaid
|
|
620
|
+
│ ├── forensics/ # run-data ingestion · flake verdicts · triage
|
|
621
|
+
│ ├── config/ # mjolnir.config.json + suppressions
|
|
622
|
+
│ ├── plugins/ # third-party rule loading (no sandbox)
|
|
623
|
+
│ └── commands/ # every subcommand
|
|
624
|
+
└── tests/
|
|
625
|
+
├── fixtures/ # must-fire / must-not-fire per rule
|
|
626
|
+
└── golden/ # frozen score regression locks
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
</details>
|
|
630
|
+
|
|
631
|
+
- **Правила — чисті функції** — `(SourceFileContext) → Finding[]`, без
|
|
632
|
+
I/O, без глобалів. Новий екосистем = один адаптер + його правила.
|
|
633
|
+
- **TypeScript/Playwright використовує AST компілятора** (ts-morph).
|
|
634
|
+
Python, Java і C# працюють на спільному regex-шарі з маскуванням
|
|
635
|
+
коментарів і рядків.
|
|
636
|
+
- Шар tree-sitter WASM AST для Java і C# існує і є наступним кроком
|
|
637
|
+
точності — він ще не підключений до синхронного скан-пайплайну.
|
|
638
|
+
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
## 📚 Документація
|
|
642
|
+
|
|
643
|
+
| Документ | Що всередині |
|
|
644
|
+
| ------------------------------------------------------ | --------------------------------------------- |
|
|
645
|
+
| [docs/SCORING.md](docs/SCORING.md) | Нормування скора + зважування за доказами |
|
|
646
|
+
| [docs/FP-AUDIT.md](docs/FP-AUDIT.md) | Виміряні частоти хибних спрацювань + методика |
|
|
647
|
+
| [docs/RULE-LIFECYCLE.md](docs/RULE-LIFECYCLE.md) | Стани правил, пригнічення, депрекація |
|
|
648
|
+
| [docs/SARIF-INTEGRATION.md](docs/SARIF-INTEGRATION.md) | SARIF-вивід + налаштування редактора/CI |
|
|
649
|
+
| [docs/rules/](docs/rules/) | Згенерований каталог за правилами |
|
|
650
|
+
| [CONTRIBUTING.md](CONTRIBUTING.md) | Dev-сетап + процес контрибуції |
|
|
651
|
+
| [CHANGELOG.md](CHANGELOG.md) | Історія релізів |
|
|
652
|
+
| [SECURITY.md](SECURITY.md) | Повідомлення про вразливості |
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
## 📈 Статус
|
|
657
|
+
|
|
658
|
+
**v0.5.x · відкрита бета.** JSON-схема і коди виходу — заморожені
|
|
659
|
+
контракти. TypeScript і Python мають найширший виміряний охват; Java і
|
|
660
|
+
C# новіші — читайте про них у
|
|
661
|
+
[таблиці тирів](#тіри-правил-і-зрілість-мов).
|
|
662
|
+
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
## 🤝 Участь у проєкті
|
|
666
|
+
|
|
667
|
+
Нові правила — найпростіший перший внесок: одна команда скаффолдить
|
|
668
|
+
правило і його must-fire- **та** must-not-fire-фікстури (згенероване
|
|
669
|
+
правило намірово падає на фікстурах, поки ви не реалізуєте реальну
|
|
670
|
+
детекцію — стаб не може вийти):
|
|
671
|
+
|
|
672
|
+
```bash
|
|
673
|
+
mjolnir create-rule QA-PW-140 --title "Screenshot without diff bound"
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
Повний dev-сетап, команди постійного гейта і закони anti-creep /
|
|
677
|
+
фікстурного фаєрвола — у [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
<div align="center">
|
|
682
|
+
|
|
683
|
+
**Перестаньте випускати тести, яким не можна довіряти.**
|
|
684
|
+
|
|
685
|
+
```bash
|
|
686
|
+
npx mjolnir-qa@latest
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
**Star ⭐ · Watch 👀 · Contribute 🤝**
|
|
690
|
+
|
|
691
|
+
Створено [Сергієм Баром](https://www.linkedin.com/in/sergeybar/)
|
|
692
|
+
|
|
693
|
+
</div>
|