mjolnir-qa 0.6.7 → 0.6.13
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 +36 -0
- package/README.md +4 -4
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +144 -50
- package/dist/mcp/stdio.mjs +144 -50
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,42 @@ Rule behavior changes (new rules, FP-rate changes against the corpus,
|
|
|
9
9
|
severity changes) are first-class entries here — rule IDs are immutable
|
|
10
10
|
once shipped, so this file is the record of what changed between versions.
|
|
11
11
|
|
|
12
|
+
## [0.6.13] — 2026-09-09
|
|
13
|
+
|
|
14
|
+
### Changes since 0.6.12
|
|
15
|
+
|
|
16
|
+
- fix(lint): {4} quantifier for the remaining literal spaces in sync-census regex
|
|
17
|
+
|
|
18
|
+
## [0.6.12] — 2026-09-09
|
|
19
|
+
|
|
20
|
+
### Changes since 0.6.11
|
|
21
|
+
|
|
22
|
+
- fix(lint): no-regex-spaces in sync-census tooling ({2} quantifier)
|
|
23
|
+
|
|
24
|
+
## [0.6.11] — 2026-09-09
|
|
25
|
+
|
|
26
|
+
### Changes since 0.6.10
|
|
27
|
+
|
|
28
|
+
- fix(lint): exclude scripts/*.mts from the typed-parser project route
|
|
29
|
+
|
|
30
|
+
## [0.6.10] — 2026-09-09
|
|
31
|
+
|
|
32
|
+
### Changes since 0.6.9
|
|
33
|
+
|
|
34
|
+
- fix(lint): scripts/*.mts one-shot tooling runs under the plain JS ruleset
|
|
35
|
+
|
|
36
|
+
## [0.6.9] — 2026-09-09
|
|
37
|
+
|
|
38
|
+
### Changes since 0.6.8
|
|
39
|
+
|
|
40
|
+
- feat(measurement): WI-14 closeout — 77/77 measured, 0 PROVISIONAL (1.0.0 census achieved)
|
|
41
|
+
|
|
42
|
+
## [0.6.8] — 2026-09-09
|
|
43
|
+
|
|
44
|
+
### Changes since 0
|
|
45
|
+
|
|
46
|
+
- feat(corpus): WI-14 harvest waves 1-5 — census 60 → 72 of 78 measured
|
|
47
|
+
|
|
12
48
|
## [0.6.7] — 2026-09-09
|
|
13
49
|
|
|
14
50
|
### Changes since 0.6.6
|
package/README.md
CHANGED
|
@@ -262,7 +262,7 @@ have no such requirement.)
|
|
|
262
262
|
|
|
263
263
|
## What Mjölnir finds
|
|
264
264
|
|
|
265
|
-
**<!-- census:total-rules -->
|
|
265
|
+
**<!-- census:total-rules -->77 rules<!-- /census:total-rules -->** in four families — **test hygiene**, **test quality**,
|
|
266
266
|
**Playwright**, **CI integrity** — over TypeScript/JavaScript, Python,
|
|
267
267
|
Java, C# and GitHub Actions YAML, covering Playwright in all four bindings
|
|
268
268
|
plus pytest, JUnit, TestNG, NUnit, xUnit, MSTest, Jest, Vitest and Mocha,
|
|
@@ -442,9 +442,9 @@ Rung by rung: [docs/TERMINOLOGY.md](docs/TERMINOLOGY.md).
|
|
|
442
442
|
|
|
443
443
|
### How much of this is measured
|
|
444
444
|
|
|
445
|
-
**<!-- census:measured-of-total -->
|
|
445
|
+
**<!-- census:measured-of-total -->77 of 77<!-- /census:measured-of-total --> rules carry a false-positive rate measured against real OSS code**
|
|
446
446
|
(≥ 10 hand-classified findings each — [docs/FP-AUDIT.md](docs/FP-AUDIT.md)).
|
|
447
|
-
The other <!-- census:unmeasured -->
|
|
447
|
+
The other <!-- census:unmeasured -->0<!-- /census:unmeasured --> ship on the author's estimate and say so, per rule, in
|
|
448
448
|
`mjolnir explain`; `mjolnir rules --unmeasured` lists them, and every scan
|
|
449
449
|
footer reports how many of the rules that actually _fired_ are measured.
|
|
450
450
|
|
|
@@ -685,7 +685,7 @@ artifacts.
|
|
|
685
685
|
product does what the requirement asked for.
|
|
686
686
|
- **A 100 is not proof of a good suite.** Whether your suite covers your
|
|
687
687
|
actual risk is a different question, and this tool does not answer it.
|
|
688
|
-
- **<!-- census:unmeasured-of-total -->
|
|
688
|
+
- **<!-- census:unmeasured-of-total -->0 of 77<!-- /census:unmeasured-of-total --> rules ship on an estimate**, not a measured rate — disclosed
|
|
689
689
|
per rule, not buried here.
|
|
690
690
|
- **E1 is not E2.** Heuristic findings are worth reading, not worth
|
|
691
691
|
applying blindly.
|
package/dist/cli.d.mts
CHANGED
|
@@ -826,7 +826,7 @@ declare const runScan: typeof runScan$1, buildUniversalRules: typeof buildUniver
|
|
|
826
826
|
* `scripts/sync-sarif-version.cjs` on release and guarded by
|
|
827
827
|
* `tests/version-consistency.spec.ts` locally.
|
|
828
828
|
*/
|
|
829
|
-
declare const CLI_VERSION = "0.6.
|
|
829
|
+
declare const CLI_VERSION = "0.6.13";
|
|
830
830
|
/** A usage-error detail: the offending token, when one exists. */
|
|
831
831
|
interface UsageErrorDetail {
|
|
832
832
|
/** The unknown flag or rejected value (e.g. `--nope`, `loud`). */
|
package/dist/cli.mjs
CHANGED
|
@@ -335,6 +335,13 @@ const MEASURED_FP = {
|
|
|
335
335
|
ciLow: 0,
|
|
336
336
|
ciHigh: .2588
|
|
337
337
|
},
|
|
338
|
+
"QA-CS-104": {
|
|
339
|
+
fpRate: 0,
|
|
340
|
+
n: 10,
|
|
341
|
+
detectorRevision: 1,
|
|
342
|
+
ciLow: 0,
|
|
343
|
+
ciHigh: .2775
|
|
344
|
+
},
|
|
338
345
|
"QA-CS-105": {
|
|
339
346
|
fpRate: .25,
|
|
340
347
|
n: 16,
|
|
@@ -342,6 +349,27 @@ const MEASURED_FP = {
|
|
|
342
349
|
ciLow: .1018,
|
|
343
350
|
ciHigh: .495
|
|
344
351
|
},
|
|
352
|
+
"QA-CS-106": {
|
|
353
|
+
fpRate: .333,
|
|
354
|
+
n: 12,
|
|
355
|
+
detectorRevision: 2,
|
|
356
|
+
ciLow: .1381,
|
|
357
|
+
ciHigh: .6094
|
|
358
|
+
},
|
|
359
|
+
"QA-CS-107": {
|
|
360
|
+
fpRate: .083,
|
|
361
|
+
n: 12,
|
|
362
|
+
detectorRevision: 1,
|
|
363
|
+
ciLow: .0149,
|
|
364
|
+
ciHigh: .3539
|
|
365
|
+
},
|
|
366
|
+
"QA-CS-109": {
|
|
367
|
+
fpRate: 0,
|
|
368
|
+
n: 10,
|
|
369
|
+
detectorRevision: 1,
|
|
370
|
+
ciLow: 0,
|
|
371
|
+
ciHigh: .2775
|
|
372
|
+
},
|
|
345
373
|
"QA-CYP-001": {
|
|
346
374
|
fpRate: .2,
|
|
347
375
|
n: 15,
|
|
@@ -349,6 +377,20 @@ const MEASURED_FP = {
|
|
|
349
377
|
ciLow: .0705,
|
|
350
378
|
ciHigh: .4519
|
|
351
379
|
},
|
|
380
|
+
"QA-CYP-002": {
|
|
381
|
+
fpRate: 0,
|
|
382
|
+
n: 10,
|
|
383
|
+
detectorRevision: 1,
|
|
384
|
+
ciLow: 0,
|
|
385
|
+
ciHigh: .2775
|
|
386
|
+
},
|
|
387
|
+
"QA-CYP-003": {
|
|
388
|
+
fpRate: 0,
|
|
389
|
+
n: 10,
|
|
390
|
+
detectorRevision: 1,
|
|
391
|
+
ciLow: 0,
|
|
392
|
+
ciHigh: .2775
|
|
393
|
+
},
|
|
352
394
|
"QA-ENV-001": {
|
|
353
395
|
fpRate: 1,
|
|
354
396
|
n: 20,
|
|
@@ -391,6 +433,20 @@ const MEASURED_FP = {
|
|
|
391
433
|
ciLow: .0279,
|
|
392
434
|
ciHigh: .301
|
|
393
435
|
},
|
|
436
|
+
"QA-JV-106": {
|
|
437
|
+
fpRate: .333,
|
|
438
|
+
n: 12,
|
|
439
|
+
detectorRevision: 2,
|
|
440
|
+
ciLow: .1381,
|
|
441
|
+
ciHigh: .6094
|
|
442
|
+
},
|
|
443
|
+
"QA-JV-107": {
|
|
444
|
+
fpRate: 0,
|
|
445
|
+
n: 10,
|
|
446
|
+
detectorRevision: 1,
|
|
447
|
+
ciLow: 0,
|
|
448
|
+
ciHigh: .2775
|
|
449
|
+
},
|
|
394
450
|
"QA-JV-109": {
|
|
395
451
|
fpRate: 0,
|
|
396
452
|
n: 18,
|
|
@@ -426,6 +482,13 @@ const MEASURED_FP = {
|
|
|
426
482
|
ciLow: 0,
|
|
427
483
|
ciHigh: .1611
|
|
428
484
|
},
|
|
485
|
+
"QA-PW-102": {
|
|
486
|
+
fpRate: .1,
|
|
487
|
+
n: 10,
|
|
488
|
+
detectorRevision: 2,
|
|
489
|
+
ciLow: .0179,
|
|
490
|
+
ciHigh: .4042
|
|
491
|
+
},
|
|
429
492
|
"QA-PW-104": {
|
|
430
493
|
fpRate: 0,
|
|
431
494
|
n: 10,
|
|
@@ -447,6 +510,13 @@ const MEASURED_FP = {
|
|
|
447
510
|
ciLow: .3318,
|
|
448
511
|
ciHigh: .769
|
|
449
512
|
},
|
|
513
|
+
"QA-PW-116": {
|
|
514
|
+
fpRate: 0,
|
|
515
|
+
n: 10,
|
|
516
|
+
detectorRevision: 1,
|
|
517
|
+
ciLow: 0,
|
|
518
|
+
ciHigh: .2775
|
|
519
|
+
},
|
|
450
520
|
"QA-PW-117": {
|
|
451
521
|
fpRate: 0,
|
|
452
522
|
n: 24,
|
|
@@ -475,6 +545,20 @@ const MEASURED_FP = {
|
|
|
475
545
|
ciLow: .2127,
|
|
476
546
|
ciHigh: .7199
|
|
477
547
|
},
|
|
548
|
+
"QA-PW-124": {
|
|
549
|
+
fpRate: .067,
|
|
550
|
+
n: 15,
|
|
551
|
+
detectorRevision: 2,
|
|
552
|
+
ciLow: .0119,
|
|
553
|
+
ciHigh: .2982
|
|
554
|
+
},
|
|
555
|
+
"QA-PW-125": {
|
|
556
|
+
fpRate: 0,
|
|
557
|
+
n: 10,
|
|
558
|
+
detectorRevision: 1,
|
|
559
|
+
ciLow: 0,
|
|
560
|
+
ciHigh: .2775
|
|
561
|
+
},
|
|
478
562
|
"QA-PW-140": {
|
|
479
563
|
fpRate: 0,
|
|
480
564
|
n: 10,
|
|
@@ -587,6 +671,13 @@ const MEASURED_FP = {
|
|
|
587
671
|
ciLow: .2459,
|
|
588
672
|
ciHigh: .5768
|
|
589
673
|
},
|
|
674
|
+
"QA-PY-101": {
|
|
675
|
+
fpRate: 0,
|
|
676
|
+
n: 10,
|
|
677
|
+
detectorRevision: 1,
|
|
678
|
+
ciLow: 0,
|
|
679
|
+
ciHigh: .2775
|
|
680
|
+
},
|
|
590
681
|
"QA-PY-103": {
|
|
591
682
|
fpRate: .08,
|
|
592
683
|
n: 25,
|
|
@@ -594,6 +685,13 @@ const MEASURED_FP = {
|
|
|
594
685
|
ciLow: .0222,
|
|
595
686
|
ciHigh: .2497
|
|
596
687
|
},
|
|
688
|
+
"QA-PY-104": {
|
|
689
|
+
fpRate: 0,
|
|
690
|
+
n: 10,
|
|
691
|
+
detectorRevision: 2,
|
|
692
|
+
ciLow: 0,
|
|
693
|
+
ciHigh: .2775
|
|
694
|
+
},
|
|
597
695
|
"QA-PY-105": {
|
|
598
696
|
fpRate: 0,
|
|
599
697
|
n: 12,
|
|
@@ -601,6 +699,48 @@ const MEASURED_FP = {
|
|
|
601
699
|
ciLow: 0,
|
|
602
700
|
ciHigh: .2425
|
|
603
701
|
},
|
|
702
|
+
"QA-PY-106": {
|
|
703
|
+
fpRate: 0,
|
|
704
|
+
n: 14,
|
|
705
|
+
detectorRevision: 1,
|
|
706
|
+
ciLow: 0,
|
|
707
|
+
ciHigh: .2153
|
|
708
|
+
},
|
|
709
|
+
"QA-PY-107": {
|
|
710
|
+
fpRate: 0,
|
|
711
|
+
n: 10,
|
|
712
|
+
detectorRevision: 1,
|
|
713
|
+
ciLow: 0,
|
|
714
|
+
ciHigh: .2775
|
|
715
|
+
},
|
|
716
|
+
"QA-PY-108": {
|
|
717
|
+
fpRate: 0,
|
|
718
|
+
n: 16,
|
|
719
|
+
detectorRevision: 1,
|
|
720
|
+
ciLow: 0,
|
|
721
|
+
ciHigh: .1936
|
|
722
|
+
},
|
|
723
|
+
"QA-SE-001": {
|
|
724
|
+
fpRate: 0,
|
|
725
|
+
n: 10,
|
|
726
|
+
detectorRevision: 1,
|
|
727
|
+
ciLow: 0,
|
|
728
|
+
ciHigh: .2775
|
|
729
|
+
},
|
|
730
|
+
"QA-SE-002": {
|
|
731
|
+
fpRate: .077,
|
|
732
|
+
n: 13,
|
|
733
|
+
detectorRevision: 1,
|
|
734
|
+
ciLow: .0137,
|
|
735
|
+
ciHigh: .3331
|
|
736
|
+
},
|
|
737
|
+
"QA-SE-003": {
|
|
738
|
+
fpRate: 0,
|
|
739
|
+
n: 11,
|
|
740
|
+
detectorRevision: 1,
|
|
741
|
+
ciLow: 0,
|
|
742
|
+
ciHigh: .2588
|
|
743
|
+
},
|
|
604
744
|
"QA-TEST-001": {
|
|
605
745
|
fpRate: .6,
|
|
606
746
|
n: 20,
|
|
@@ -5933,6 +6073,7 @@ const pwGlobalSetupSharedState = defineRule({
|
|
|
5933
6073
|
findingType: "heuristic-risk",
|
|
5934
6074
|
qaImpact: "HYGIENE",
|
|
5935
6075
|
appliesTo: "test-files",
|
|
6076
|
+
tier: "extended",
|
|
5936
6077
|
languages: ["typescript", "javascript"],
|
|
5937
6078
|
frameworks: ["playwright"],
|
|
5938
6079
|
falsePositiveRisk: "medium",
|
|
@@ -6596,52 +6737,6 @@ const pyPwSyncAsyncMix = defineRule({
|
|
|
6596
6737
|
}
|
|
6597
6738
|
});
|
|
6598
6739
|
//#endregion
|
|
6599
|
-
//#region src/rules/python/qa-py-102-pw-hard-sleep.ts
|
|
6600
|
-
/**
|
|
6601
|
-
* QA-PY-102 — Playwright-Python: time.sleep() instead of auto-waiting.
|
|
6602
|
-
* Severity: warning · Confidence: high · deterministic-defect
|
|
6603
|
-
* The #1 Python-Playwright flake source: sleeping "until the page is
|
|
6604
|
-
* ready" instead of waiting on a locator condition.
|
|
6605
|
-
*/
|
|
6606
|
-
const pyPwHardSleep = defineRule({
|
|
6607
|
-
id: "QA-PY-102",
|
|
6608
|
-
category: "QA-PW",
|
|
6609
|
-
title: "time.sleep() in Playwright test",
|
|
6610
|
-
severity: "warning",
|
|
6611
|
-
confidence: "high",
|
|
6612
|
-
findingType: "deterministic-defect",
|
|
6613
|
-
qaImpact: "FLAKY-RISK",
|
|
6614
|
-
appliesTo: "python",
|
|
6615
|
-
languages: ["python"],
|
|
6616
|
-
frameworks: ["pytest-playwright", "playwright"],
|
|
6617
|
-
falsePositiveRisk: "low",
|
|
6618
|
-
autofix: false,
|
|
6619
|
-
detectionStrategy: "LEXICAL",
|
|
6620
|
-
introduced: "0.3.8",
|
|
6621
|
-
tier: "quarantine",
|
|
6622
|
-
run(ctx) {
|
|
6623
|
-
const text = ctx.codeText ?? ctx.text;
|
|
6624
|
-
const findings = [];
|
|
6625
|
-
if (!ctx.path.endsWith(".py")) return findings;
|
|
6626
|
-
if (!/playwright/i.test(text)) return findings;
|
|
6627
|
-
const re = /\btime\.sleep\s*\(/g;
|
|
6628
|
-
let m;
|
|
6629
|
-
while ((m = re.exec(text)) !== null) findings.push({
|
|
6630
|
-
severity: "warning",
|
|
6631
|
-
confidence: "high",
|
|
6632
|
-
findingType: "deterministic-defect",
|
|
6633
|
-
qaImpact: "FLAKY-RISK",
|
|
6634
|
-
file: ctx.path,
|
|
6635
|
-
line: lineAt(text, m.index),
|
|
6636
|
-
column: colAt(text, m.index),
|
|
6637
|
-
message: "`time.sleep()` used to wait for UI state.",
|
|
6638
|
-
why: "Fixed sleeps guess at timing: too short → flaky failures under load, too long → slow suite. Playwright locators already auto-wait for the element to be actionable.",
|
|
6639
|
-
fix: "Replace with `expect(page.get_by_text(...)).to_be_visible()` or `page.wait_for_selector`."
|
|
6640
|
-
});
|
|
6641
|
-
return findings;
|
|
6642
|
-
}
|
|
6643
|
-
});
|
|
6644
|
-
//#endregion
|
|
6645
6740
|
//#region src/rules/python/qa-py-103-wait-for-timeout.ts
|
|
6646
6741
|
/**
|
|
6647
6742
|
* QA-PY-103 — Playwright-Python: wait_for_timeout() as synchronization.
|
|
@@ -9150,7 +9245,6 @@ const RULES = [
|
|
|
9150
9245
|
pwLocatorNormalize,
|
|
9151
9246
|
pwCodegenArtifact,
|
|
9152
9247
|
pyPwSyncAsyncMix,
|
|
9153
|
-
pyPwHardSleep,
|
|
9154
9248
|
pyPwWaitForTimeout,
|
|
9155
9249
|
pyPwNoAssertions,
|
|
9156
9250
|
...sharedPageFamily,
|
|
@@ -9208,6 +9302,7 @@ const RETIRED_RULE_IDS = [
|
|
|
9208
9302
|
"QA-PY-006",
|
|
9209
9303
|
"QA-PY-008",
|
|
9210
9304
|
"QA-PY-010",
|
|
9305
|
+
"QA-PY-102",
|
|
9211
9306
|
"QA-JV-108",
|
|
9212
9307
|
"QA-CS-108",
|
|
9213
9308
|
"QA-JV-110",
|
|
@@ -12018,7 +12113,6 @@ const BLUNT_MESSAGES = {
|
|
|
12018
12113
|
"QA-PW-101": "A hard sleep in a test is a prayer, not a synchronization strategy.",
|
|
12019
12114
|
"QA-TEST-004": "A hard sleep in a test is a prayer, not a synchronization strategy.",
|
|
12020
12115
|
"QA-PY-005": "time.sleep() in a test is a prayer. Python doesn't negotiate with timeouts either.",
|
|
12021
|
-
"QA-PY-102": "time.sleep() inside a Playwright test — the browser doesn't pause because you asked nicely.",
|
|
12022
12116
|
"QA-PY-103": "wait_for_timeout() as synchronization — the Playwright-Python edition of hoping.",
|
|
12023
12117
|
"QA-JV-102": "Thread.sleep() in a test is a prayer. The JVM doesn't negotiate with timeouts.",
|
|
12024
12118
|
"QA-JV-105": "waitForTimeout is the Java version of closing your eyes and hoping.",
|
|
@@ -12659,7 +12753,7 @@ function renderSarif(result, repoRootUri) {
|
|
|
12659
12753
|
tool: { driver: {
|
|
12660
12754
|
name: "Mjölnir",
|
|
12661
12755
|
informationUri: "https://github.com/Sergey-Bar/Mjolnir",
|
|
12662
|
-
version: "0.6.
|
|
12756
|
+
version: "0.6.13",
|
|
12663
12757
|
rules: [...rules.values()].map((r) => {
|
|
12664
12758
|
const meta = RULES.find((x) => x.id === r.id);
|
|
12665
12759
|
return {
|
|
@@ -19063,7 +19157,7 @@ const { runScan, buildUniversalRules, fallbackWorkspace, pathMatchesGlob, isVali
|
|
|
19063
19157
|
* `scripts/sync-sarif-version.cjs` on release and guarded by
|
|
19064
19158
|
* `tests/version-consistency.spec.ts` locally.
|
|
19065
19159
|
*/
|
|
19066
|
-
const CLI_VERSION = "0.6.
|
|
19160
|
+
const CLI_VERSION = "0.6.13";
|
|
19067
19161
|
function parseArgs(argv, onError) {
|
|
19068
19162
|
const args = {
|
|
19069
19163
|
target: ".",
|
package/dist/mcp/stdio.mjs
CHANGED
|
@@ -334,6 +334,13 @@ const MEASURED_FP = {
|
|
|
334
334
|
ciLow: 0,
|
|
335
335
|
ciHigh: .2588
|
|
336
336
|
},
|
|
337
|
+
"QA-CS-104": {
|
|
338
|
+
fpRate: 0,
|
|
339
|
+
n: 10,
|
|
340
|
+
detectorRevision: 1,
|
|
341
|
+
ciLow: 0,
|
|
342
|
+
ciHigh: .2775
|
|
343
|
+
},
|
|
337
344
|
"QA-CS-105": {
|
|
338
345
|
fpRate: .25,
|
|
339
346
|
n: 16,
|
|
@@ -341,6 +348,27 @@ const MEASURED_FP = {
|
|
|
341
348
|
ciLow: .1018,
|
|
342
349
|
ciHigh: .495
|
|
343
350
|
},
|
|
351
|
+
"QA-CS-106": {
|
|
352
|
+
fpRate: .333,
|
|
353
|
+
n: 12,
|
|
354
|
+
detectorRevision: 2,
|
|
355
|
+
ciLow: .1381,
|
|
356
|
+
ciHigh: .6094
|
|
357
|
+
},
|
|
358
|
+
"QA-CS-107": {
|
|
359
|
+
fpRate: .083,
|
|
360
|
+
n: 12,
|
|
361
|
+
detectorRevision: 1,
|
|
362
|
+
ciLow: .0149,
|
|
363
|
+
ciHigh: .3539
|
|
364
|
+
},
|
|
365
|
+
"QA-CS-109": {
|
|
366
|
+
fpRate: 0,
|
|
367
|
+
n: 10,
|
|
368
|
+
detectorRevision: 1,
|
|
369
|
+
ciLow: 0,
|
|
370
|
+
ciHigh: .2775
|
|
371
|
+
},
|
|
344
372
|
"QA-CYP-001": {
|
|
345
373
|
fpRate: .2,
|
|
346
374
|
n: 15,
|
|
@@ -348,6 +376,20 @@ const MEASURED_FP = {
|
|
|
348
376
|
ciLow: .0705,
|
|
349
377
|
ciHigh: .4519
|
|
350
378
|
},
|
|
379
|
+
"QA-CYP-002": {
|
|
380
|
+
fpRate: 0,
|
|
381
|
+
n: 10,
|
|
382
|
+
detectorRevision: 1,
|
|
383
|
+
ciLow: 0,
|
|
384
|
+
ciHigh: .2775
|
|
385
|
+
},
|
|
386
|
+
"QA-CYP-003": {
|
|
387
|
+
fpRate: 0,
|
|
388
|
+
n: 10,
|
|
389
|
+
detectorRevision: 1,
|
|
390
|
+
ciLow: 0,
|
|
391
|
+
ciHigh: .2775
|
|
392
|
+
},
|
|
351
393
|
"QA-ENV-001": {
|
|
352
394
|
fpRate: 1,
|
|
353
395
|
n: 20,
|
|
@@ -390,6 +432,20 @@ const MEASURED_FP = {
|
|
|
390
432
|
ciLow: .0279,
|
|
391
433
|
ciHigh: .301
|
|
392
434
|
},
|
|
435
|
+
"QA-JV-106": {
|
|
436
|
+
fpRate: .333,
|
|
437
|
+
n: 12,
|
|
438
|
+
detectorRevision: 2,
|
|
439
|
+
ciLow: .1381,
|
|
440
|
+
ciHigh: .6094
|
|
441
|
+
},
|
|
442
|
+
"QA-JV-107": {
|
|
443
|
+
fpRate: 0,
|
|
444
|
+
n: 10,
|
|
445
|
+
detectorRevision: 1,
|
|
446
|
+
ciLow: 0,
|
|
447
|
+
ciHigh: .2775
|
|
448
|
+
},
|
|
393
449
|
"QA-JV-109": {
|
|
394
450
|
fpRate: 0,
|
|
395
451
|
n: 18,
|
|
@@ -425,6 +481,13 @@ const MEASURED_FP = {
|
|
|
425
481
|
ciLow: 0,
|
|
426
482
|
ciHigh: .1611
|
|
427
483
|
},
|
|
484
|
+
"QA-PW-102": {
|
|
485
|
+
fpRate: .1,
|
|
486
|
+
n: 10,
|
|
487
|
+
detectorRevision: 2,
|
|
488
|
+
ciLow: .0179,
|
|
489
|
+
ciHigh: .4042
|
|
490
|
+
},
|
|
428
491
|
"QA-PW-104": {
|
|
429
492
|
fpRate: 0,
|
|
430
493
|
n: 10,
|
|
@@ -446,6 +509,13 @@ const MEASURED_FP = {
|
|
|
446
509
|
ciLow: .3318,
|
|
447
510
|
ciHigh: .769
|
|
448
511
|
},
|
|
512
|
+
"QA-PW-116": {
|
|
513
|
+
fpRate: 0,
|
|
514
|
+
n: 10,
|
|
515
|
+
detectorRevision: 1,
|
|
516
|
+
ciLow: 0,
|
|
517
|
+
ciHigh: .2775
|
|
518
|
+
},
|
|
449
519
|
"QA-PW-117": {
|
|
450
520
|
fpRate: 0,
|
|
451
521
|
n: 24,
|
|
@@ -474,6 +544,20 @@ const MEASURED_FP = {
|
|
|
474
544
|
ciLow: .2127,
|
|
475
545
|
ciHigh: .7199
|
|
476
546
|
},
|
|
547
|
+
"QA-PW-124": {
|
|
548
|
+
fpRate: .067,
|
|
549
|
+
n: 15,
|
|
550
|
+
detectorRevision: 2,
|
|
551
|
+
ciLow: .0119,
|
|
552
|
+
ciHigh: .2982
|
|
553
|
+
},
|
|
554
|
+
"QA-PW-125": {
|
|
555
|
+
fpRate: 0,
|
|
556
|
+
n: 10,
|
|
557
|
+
detectorRevision: 1,
|
|
558
|
+
ciLow: 0,
|
|
559
|
+
ciHigh: .2775
|
|
560
|
+
},
|
|
477
561
|
"QA-PW-140": {
|
|
478
562
|
fpRate: 0,
|
|
479
563
|
n: 10,
|
|
@@ -586,6 +670,13 @@ const MEASURED_FP = {
|
|
|
586
670
|
ciLow: .2459,
|
|
587
671
|
ciHigh: .5768
|
|
588
672
|
},
|
|
673
|
+
"QA-PY-101": {
|
|
674
|
+
fpRate: 0,
|
|
675
|
+
n: 10,
|
|
676
|
+
detectorRevision: 1,
|
|
677
|
+
ciLow: 0,
|
|
678
|
+
ciHigh: .2775
|
|
679
|
+
},
|
|
589
680
|
"QA-PY-103": {
|
|
590
681
|
fpRate: .08,
|
|
591
682
|
n: 25,
|
|
@@ -593,6 +684,13 @@ const MEASURED_FP = {
|
|
|
593
684
|
ciLow: .0222,
|
|
594
685
|
ciHigh: .2497
|
|
595
686
|
},
|
|
687
|
+
"QA-PY-104": {
|
|
688
|
+
fpRate: 0,
|
|
689
|
+
n: 10,
|
|
690
|
+
detectorRevision: 2,
|
|
691
|
+
ciLow: 0,
|
|
692
|
+
ciHigh: .2775
|
|
693
|
+
},
|
|
596
694
|
"QA-PY-105": {
|
|
597
695
|
fpRate: 0,
|
|
598
696
|
n: 12,
|
|
@@ -600,6 +698,48 @@ const MEASURED_FP = {
|
|
|
600
698
|
ciLow: 0,
|
|
601
699
|
ciHigh: .2425
|
|
602
700
|
},
|
|
701
|
+
"QA-PY-106": {
|
|
702
|
+
fpRate: 0,
|
|
703
|
+
n: 14,
|
|
704
|
+
detectorRevision: 1,
|
|
705
|
+
ciLow: 0,
|
|
706
|
+
ciHigh: .2153
|
|
707
|
+
},
|
|
708
|
+
"QA-PY-107": {
|
|
709
|
+
fpRate: 0,
|
|
710
|
+
n: 10,
|
|
711
|
+
detectorRevision: 1,
|
|
712
|
+
ciLow: 0,
|
|
713
|
+
ciHigh: .2775
|
|
714
|
+
},
|
|
715
|
+
"QA-PY-108": {
|
|
716
|
+
fpRate: 0,
|
|
717
|
+
n: 16,
|
|
718
|
+
detectorRevision: 1,
|
|
719
|
+
ciLow: 0,
|
|
720
|
+
ciHigh: .1936
|
|
721
|
+
},
|
|
722
|
+
"QA-SE-001": {
|
|
723
|
+
fpRate: 0,
|
|
724
|
+
n: 10,
|
|
725
|
+
detectorRevision: 1,
|
|
726
|
+
ciLow: 0,
|
|
727
|
+
ciHigh: .2775
|
|
728
|
+
},
|
|
729
|
+
"QA-SE-002": {
|
|
730
|
+
fpRate: .077,
|
|
731
|
+
n: 13,
|
|
732
|
+
detectorRevision: 1,
|
|
733
|
+
ciLow: .0137,
|
|
734
|
+
ciHigh: .3331
|
|
735
|
+
},
|
|
736
|
+
"QA-SE-003": {
|
|
737
|
+
fpRate: 0,
|
|
738
|
+
n: 11,
|
|
739
|
+
detectorRevision: 1,
|
|
740
|
+
ciLow: 0,
|
|
741
|
+
ciHigh: .2588
|
|
742
|
+
},
|
|
603
743
|
"QA-TEST-001": {
|
|
604
744
|
fpRate: .6,
|
|
605
745
|
n: 20,
|
|
@@ -5932,6 +6072,7 @@ const pwGlobalSetupSharedState = defineRule({
|
|
|
5932
6072
|
findingType: "heuristic-risk",
|
|
5933
6073
|
qaImpact: "HYGIENE",
|
|
5934
6074
|
appliesTo: "test-files",
|
|
6075
|
+
tier: "extended",
|
|
5935
6076
|
languages: ["typescript", "javascript"],
|
|
5936
6077
|
frameworks: ["playwright"],
|
|
5937
6078
|
falsePositiveRisk: "medium",
|
|
@@ -6595,52 +6736,6 @@ const pyPwSyncAsyncMix = defineRule({
|
|
|
6595
6736
|
}
|
|
6596
6737
|
});
|
|
6597
6738
|
//#endregion
|
|
6598
|
-
//#region src/rules/python/qa-py-102-pw-hard-sleep.ts
|
|
6599
|
-
/**
|
|
6600
|
-
* QA-PY-102 — Playwright-Python: time.sleep() instead of auto-waiting.
|
|
6601
|
-
* Severity: warning · Confidence: high · deterministic-defect
|
|
6602
|
-
* The #1 Python-Playwright flake source: sleeping "until the page is
|
|
6603
|
-
* ready" instead of waiting on a locator condition.
|
|
6604
|
-
*/
|
|
6605
|
-
const pyPwHardSleep = defineRule({
|
|
6606
|
-
id: "QA-PY-102",
|
|
6607
|
-
category: "QA-PW",
|
|
6608
|
-
title: "time.sleep() in Playwright test",
|
|
6609
|
-
severity: "warning",
|
|
6610
|
-
confidence: "high",
|
|
6611
|
-
findingType: "deterministic-defect",
|
|
6612
|
-
qaImpact: "FLAKY-RISK",
|
|
6613
|
-
appliesTo: "python",
|
|
6614
|
-
languages: ["python"],
|
|
6615
|
-
frameworks: ["pytest-playwright", "playwright"],
|
|
6616
|
-
falsePositiveRisk: "low",
|
|
6617
|
-
autofix: false,
|
|
6618
|
-
detectionStrategy: "LEXICAL",
|
|
6619
|
-
introduced: "0.3.8",
|
|
6620
|
-
tier: "quarantine",
|
|
6621
|
-
run(ctx) {
|
|
6622
|
-
const text = ctx.codeText ?? ctx.text;
|
|
6623
|
-
const findings = [];
|
|
6624
|
-
if (!ctx.path.endsWith(".py")) return findings;
|
|
6625
|
-
if (!/playwright/i.test(text)) return findings;
|
|
6626
|
-
const re = /\btime\.sleep\s*\(/g;
|
|
6627
|
-
let m;
|
|
6628
|
-
while ((m = re.exec(text)) !== null) findings.push({
|
|
6629
|
-
severity: "warning",
|
|
6630
|
-
confidence: "high",
|
|
6631
|
-
findingType: "deterministic-defect",
|
|
6632
|
-
qaImpact: "FLAKY-RISK",
|
|
6633
|
-
file: ctx.path,
|
|
6634
|
-
line: lineAt(text, m.index),
|
|
6635
|
-
column: colAt(text, m.index),
|
|
6636
|
-
message: "`time.sleep()` used to wait for UI state.",
|
|
6637
|
-
why: "Fixed sleeps guess at timing: too short → flaky failures under load, too long → slow suite. Playwright locators already auto-wait for the element to be actionable.",
|
|
6638
|
-
fix: "Replace with `expect(page.get_by_text(...)).to_be_visible()` or `page.wait_for_selector`."
|
|
6639
|
-
});
|
|
6640
|
-
return findings;
|
|
6641
|
-
}
|
|
6642
|
-
});
|
|
6643
|
-
//#endregion
|
|
6644
6739
|
//#region src/rules/python/qa-py-103-wait-for-timeout.ts
|
|
6645
6740
|
/**
|
|
6646
6741
|
* QA-PY-103 — Playwright-Python: wait_for_timeout() as synchronization.
|
|
@@ -9149,7 +9244,6 @@ const RULES = [
|
|
|
9149
9244
|
pwLocatorNormalize,
|
|
9150
9245
|
pwCodegenArtifact,
|
|
9151
9246
|
pyPwSyncAsyncMix,
|
|
9152
|
-
pyPwHardSleep,
|
|
9153
9247
|
pyPwWaitForTimeout,
|
|
9154
9248
|
pyPwNoAssertions,
|
|
9155
9249
|
...sharedPageFamily,
|
|
@@ -9207,6 +9301,7 @@ const RETIRED_RULE_IDS = [
|
|
|
9207
9301
|
"QA-PY-006",
|
|
9208
9302
|
"QA-PY-008",
|
|
9209
9303
|
"QA-PY-010",
|
|
9304
|
+
"QA-PY-102",
|
|
9210
9305
|
"QA-JV-108",
|
|
9211
9306
|
"QA-CS-108",
|
|
9212
9307
|
"QA-JV-110",
|
|
@@ -12017,7 +12112,6 @@ const BLUNT_MESSAGES = {
|
|
|
12017
12112
|
"QA-PW-101": "A hard sleep in a test is a prayer, not a synchronization strategy.",
|
|
12018
12113
|
"QA-TEST-004": "A hard sleep in a test is a prayer, not a synchronization strategy.",
|
|
12019
12114
|
"QA-PY-005": "time.sleep() in a test is a prayer. Python doesn't negotiate with timeouts either.",
|
|
12020
|
-
"QA-PY-102": "time.sleep() inside a Playwright test — the browser doesn't pause because you asked nicely.",
|
|
12021
12115
|
"QA-PY-103": "wait_for_timeout() as synchronization — the Playwright-Python edition of hoping.",
|
|
12022
12116
|
"QA-JV-102": "Thread.sleep() in a test is a prayer. The JVM doesn't negotiate with timeouts.",
|
|
12023
12117
|
"QA-JV-105": "waitForTimeout is the Java version of closing your eyes and hoping.",
|
|
@@ -12658,7 +12752,7 @@ function renderSarif(result, repoRootUri) {
|
|
|
12658
12752
|
tool: { driver: {
|
|
12659
12753
|
name: "Mjölnir",
|
|
12660
12754
|
informationUri: "https://github.com/Sergey-Bar/Mjolnir",
|
|
12661
|
-
version: "0.6.
|
|
12755
|
+
version: "0.6.13",
|
|
12662
12756
|
rules: [...rules.values()].map((r) => {
|
|
12663
12757
|
const meta = RULES.find((x) => x.id === r.id);
|
|
12664
12758
|
return {
|
|
@@ -18638,7 +18732,7 @@ const { runScan, buildUniversalRules, fallbackWorkspace, pathMatchesGlob, isVali
|
|
|
18638
18732
|
* `scripts/sync-sarif-version.cjs` on release and guarded by
|
|
18639
18733
|
* `tests/version-consistency.spec.ts` locally.
|
|
18640
18734
|
*/
|
|
18641
|
-
const CLI_VERSION = "0.6.
|
|
18735
|
+
const CLI_VERSION = "0.6.13";
|
|
18642
18736
|
function parseArgs(argv, onError) {
|
|
18643
18737
|
const args = {
|
|
18644
18738
|
target: ".",
|
package/package.json
CHANGED