@gfdelarue/testkit 0.4.0 → 0.4.1

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.
Files changed (122) hide show
  1. package/README.md +125 -0
  2. package/dist/app/bugs/areas.d.ts +38 -0
  3. package/dist/app/bugs/areas.js +1 -0
  4. package/dist/app/bugs/assessment.d.ts +9 -0
  5. package/dist/app/bugs/assessment.js +1 -0
  6. package/dist/app/bugs/categories.d.ts +41 -0
  7. package/dist/app/bugs/categories.js +1 -0
  8. package/dist/app/bugs/ci-report.d.ts +21 -0
  9. package/dist/app/bugs/ci-report.js +2 -0
  10. package/dist/app/bugs/ci-summary.d.ts +13 -0
  11. package/dist/app/bugs/ci-summary.js +5 -0
  12. package/dist/app/bugs/draft.d.ts +70 -0
  13. package/dist/app/bugs/draft.js +4 -0
  14. package/dist/app/bugs/facts.d.ts +48 -0
  15. package/dist/app/bugs/facts.js +2 -0
  16. package/dist/app/bugs/file.d.ts +45 -0
  17. package/dist/app/bugs/file.js +1 -0
  18. package/dist/app/bugs/gate.d.ts +22 -0
  19. package/dist/app/bugs/gate.js +1 -0
  20. package/dist/app/bugs/guide.d.ts +20 -0
  21. package/dist/app/bugs/guide.js +8 -0
  22. package/dist/app/bugs/issue.d.ts +18 -0
  23. package/dist/app/bugs/issue.js +6 -0
  24. package/dist/app/bugs/known-failures.d.ts +49 -0
  25. package/dist/app/bugs/known-failures.js +1 -0
  26. package/dist/app/bugs/record.d.ts +14 -0
  27. package/dist/app/bugs/record.js +2 -0
  28. package/dist/app/bugs/run-reading.d.ts +51 -0
  29. package/dist/app/bugs/run-reading.js +1 -0
  30. package/dist/app/bugs/store.d.ts +34 -0
  31. package/dist/app/bugs/store.js +2 -0
  32. package/dist/app/campaigns/brief.d.ts +7 -0
  33. package/dist/app/campaigns/brief.js +10 -0
  34. package/dist/app/campaigns/campaign.d.ts +14 -0
  35. package/dist/app/campaigns/campaign.js +1 -0
  36. package/dist/app/campaigns/index.d.ts +38 -0
  37. package/dist/app/campaigns/index.js +2 -0
  38. package/dist/app/campaigns/store.d.ts +5 -0
  39. package/dist/app/campaigns/store.js +1 -0
  40. package/dist/app/campaigns/summary.d.ts +13 -0
  41. package/dist/app/campaigns/summary.js +8 -0
  42. package/dist/app/run/engine-execution.d.ts +1 -0
  43. package/dist/app/run/engine-execution.js +1 -1
  44. package/dist/app/run/failure-details.d.ts +1 -0
  45. package/dist/app/run/failure-details.js +1 -1
  46. package/dist/app/tracker/index.d.ts +8 -0
  47. package/dist/app/tracker/index.js +1 -0
  48. package/dist/app/tracker/remote.d.ts +13 -0
  49. package/dist/app/tracker/remote.js +1 -0
  50. package/dist/app/tracker/token.d.ts +7 -0
  51. package/dist/app/tracker/token.js +5 -0
  52. package/dist/cli/command-catalog.d.ts +26 -0
  53. package/dist/cli/command-catalog.js +1 -1
  54. package/dist/cli/command-registry.js +1 -1
  55. package/dist/cli/commands/bug/file.d.ts +16 -0
  56. package/dist/cli/commands/bug/file.js +2 -0
  57. package/dist/cli/commands/bug/guide.d.ts +5 -0
  58. package/dist/cli/commands/bug/guide.js +1 -0
  59. package/dist/cli/commands/bug/record.d.ts +9 -0
  60. package/dist/cli/commands/bug/record.js +2 -0
  61. package/dist/cli/commands/campaign/finish.d.ts +10 -0
  62. package/dist/cli/commands/campaign/finish.js +1 -0
  63. package/dist/cli/commands/campaign/start.d.ts +7 -0
  64. package/dist/cli/commands/campaign/start.js +1 -0
  65. package/dist/cli/commands/campaign/status.d.ts +13 -0
  66. package/dist/cli/commands/campaign/status.js +2 -0
  67. package/dist/cli/commands/ci/report.d.ts +15 -0
  68. package/dist/cli/commands/ci/report.js +2 -0
  69. package/dist/cli/commands/run.d.ts +1 -1
  70. package/dist/cli/commands/run.js +2 -1
  71. package/dist/cli/renderers/bug/text.d.ts +8 -0
  72. package/dist/cli/renderers/bug/text.js +1 -0
  73. package/dist/cli/renderers/campaign/text.d.ts +2 -0
  74. package/dist/cli/renderers/campaign/text.js +1 -0
  75. package/dist/cli/renderers/ci/text.d.ts +2 -0
  76. package/dist/cli/renderers/ci/text.js +1 -0
  77. package/dist/config/authoring/index.d.ts +10 -0
  78. package/dist/config/loader/bugs.d.ts +15 -0
  79. package/dist/config/loader/bugs.js +1 -0
  80. package/dist/engines/adapters/execution.d.ts +4 -0
  81. package/dist/engines/adapters/execution.js +1 -1
  82. package/dist/engines/k6/adapter/executor.js +1 -1
  83. package/dist/engines/k6/runtime/checks.js +1 -1
  84. package/dist/engines/k6/runtime/http-assertions.d.ts +2 -2
  85. package/dist/engines/k6/runtime/http-assertions.js +1 -1
  86. package/dist/engines/k6/runtime/types.d.ts +2 -0
  87. package/dist/engines/node-vitest/adapter/evidence.js +2 -2
  88. package/dist/engines/node-vitest/adapter/failure-details.d.ts +1 -0
  89. package/dist/engines/node-vitest/adapter/failure-details.js +2 -2
  90. package/dist/engines/playwright/adapter/adapter.js +1 -1
  91. package/dist/engines/playwright/adapter/attachments.d.ts +1 -0
  92. package/dist/engines/playwright/adapter/config.js +3 -3
  93. package/dist/engines/playwright/adapter/evidence.js +1 -1
  94. package/dist/engines/playwright/adapter/reporter.js +1 -1
  95. package/dist/kernel/redaction.d.ts +1 -0
  96. package/dist/kernel/redaction.js +1 -1
  97. package/dist/process/execa-bundle.json +1 -1
  98. package/dist/process/files/json-file.d.ts +1 -0
  99. package/dist/process/files/json-file.js +1 -1
  100. package/dist/repo/history.d.ts +17 -0
  101. package/dist/repo/history.js +4 -0
  102. package/dist/repo/tree-listing.d.ts +10 -0
  103. package/dist/repo/tree-listing.js +1 -0
  104. package/dist/tracker/gitea.d.ts +14 -0
  105. package/dist/tracker/gitea.js +1 -0
  106. package/dist/tracker/github.d.ts +14 -0
  107. package/dist/tracker/github.js +1 -0
  108. package/dist/tracker/http.d.ts +33 -0
  109. package/dist/tracker/http.js +1 -0
  110. package/dist/tracker/index.d.ts +4 -0
  111. package/dist/tracker/index.js +1 -0
  112. package/dist/tracker/redaction.d.ts +4 -0
  113. package/dist/tracker/redaction.js +2 -0
  114. package/dist/tracker/requests.d.ts +17 -0
  115. package/dist/tracker/requests.js +1 -0
  116. package/dist/tracker/responses.d.ts +24 -0
  117. package/dist/tracker/responses.js +1 -0
  118. package/dist/tracker/types.d.ts +51 -0
  119. package/dist/tracker/types.js +1 -0
  120. package/docs/bug-records.md +303 -0
  121. package/package.json +2 -1
  122. package/testkit-config-schema.md +50 -0
@@ -0,0 +1,303 @@
1
+ # Bug records
2
+
3
+ This page is the reference for what Testkit writes to GitHub or Gitea when it files a bug or reports a CI run. Tools that read Testkit's issues and statuses depend on it.
4
+
5
+ The format version is `1`. The facts block carries it as `v`.
6
+
7
+ ## Labels
8
+
9
+ Every issue that Testkit files carries the `testkit` label. Testkit and its readers find Testkit's issues by that label and by nothing else.
10
+
11
+ | Label | Colour | Meaning |
12
+ | ------------------- | -------- | -------------------------------------------------------------------------- |
13
+ | `testkit` | `0f766e` | Filed by Testkit with a regression test. |
14
+ | `severity:critical` | `9f1239` | The product is unsafe to run or to trust. |
15
+ | `severity:high` | `e11d48` | A core behaviour is broken with no acceptable workaround. |
16
+ | `severity:medium` | `d97706` | A behaviour is broken and a workaround exists, or the reach is limited. |
17
+ | `severity:low` | `4d7c0f` | A minor defect with little consequence. |
18
+ | `area:<id>` | `475569` | The product area that holds the fault. The description is the area's name. |
19
+
20
+ Testkit creates a label that does not exist yet and never changes one that does.
21
+
22
+ Severity and area are labels because people re-triage both. A reader takes the bug's severity from the `severity:` label. A reader takes the bug's area id from the `area:` label when one is present and from the facts block otherwise. An issue with several `severity:` labels or several `area:` labels has no severity or no area label for this purpose.
23
+
24
+ ## Issue body
25
+
26
+ The body has five sections and ends with the facts block.
27
+
28
+ ````markdown
29
+ ## Expected behaviour
30
+
31
+ A refund pays out once.
32
+
33
+ ## Authority
34
+
35
+ `docs/refunds.md`, "Refunds are idempotent on the order id".
36
+
37
+ ## Observed behaviour
38
+
39
+ Two overlapping refund requests for one order both pay out.
40
+
41
+ ## Root cause
42
+
43
+ `refund()` reads the order's refund state and writes the payout without a lock.
44
+
45
+ ## Reproduce
46
+
47
+ ```sh
48
+ npx testkit run --file src/checkout/__testkit__/refund.contract.testkit.ts
49
+ ```
50
+
51
+ Test: `src/checkout/__testkit__/refund.contract.testkit.ts`, "a refund pays out once".
52
+ Found at `4f0c2d1e9b7a4c3d8e6f5a4b3c2d1e0f9a8b7c6d`.
53
+ Fault: `src/checkout/refunds.ts`, lines 41 to 58.
54
+
55
+ <details>
56
+ <summary>Testkit facts</summary>
57
+
58
+ <!-- testkit-facts:start -->
59
+
60
+ ```json
61
+ {
62
+ "v": 1,
63
+ "cause": "race-condition",
64
+ "impact": "financial-error",
65
+ "confidence": { "cause": "high", "impact": "high" },
66
+ "escape": "no-test",
67
+ "caughtBy": "campaign",
68
+ "campaign": "refunds-2026-10",
69
+ "testType": "contract",
70
+ "test": {
71
+ "file": "src/checkout/__testkit__/refund.contract.testkit.ts",
72
+ "title": "a refund pays out once"
73
+ },
74
+ "foundAt": "4f0c2d1e9b7a4c3d8e6f5a4b3c2d1e0f9a8b7c6d",
75
+ "fault": { "path": "src/checkout/refunds.ts", "lines": [41, 58] },
76
+ "area": { "id": "checkout", "name": "Checkout", "share": 0.14 },
77
+ "introducedBy": "8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b",
78
+ "introducedMethod": "blame"
79
+ }
80
+ ```
81
+
82
+ <!-- testkit-facts:end -->
83
+
84
+ </details>
85
+ ````
86
+
87
+ The reproduce command runs from the repository root. When `testkit.config.ts` is in a subdirectory, the command carries `--dir <directory>` and the `--file` path is relative to that directory. The `Test:` and `Fault:` lines always give paths from the repository root.
88
+
89
+ A fault on one line reads `line 41`.
90
+
91
+ An issue for a k6 script that fails as a whole covers the whole file. Its facts block has no `test.title`, and its `Test:` line quotes the `test.title` of the bug record as the finder's description of the script.
92
+
93
+ ```markdown
94
+ Test: `src/health/__testkit__/health.int.testkit.ts`, the whole script, recorded as "the health endpoint answers".
95
+ ```
96
+
97
+ Testkit redacts the secrets it recognises in the title, in every section, and in `test.title`. It does so when it records the bug and again when it files the issue. It recognises these forms.
98
+
99
+ - The value of a `KEY=value` assignment whose key names a secret.
100
+ - The password in a URL.
101
+ - The value of an `Authorization`, `Proxy-Authorization`, `Cookie`, or `Set-Cookie` header.
102
+ - A JSON string value whose key names a secret.
103
+
104
+ A key names a secret when it contains `PASSWORD`, `TOKEN`, `SECRET`, `PRIVATE_KEY`, `AUTH`, `COOKIE`, `SESSION`, or `API_KEY` in any letter case. Testkit cannot recognise every secret, so the finder must still keep credentials, tokens, and passwords out of the record.
105
+
106
+ An assigned value ends at the first space, with three exceptions.
107
+
108
+ - A value that starts with `"` or `'` ends at the next such quote on its line. When the line has no such quote, the value ends with the line.
109
+ - A value that starts with `Basic`, `Bearer`, `Digest`, `Negotiate`, or `Token` and then a space or a tab ends with the line.
110
+ - Under a key that ends in `_URL`, Testkit keeps a value that starts with a scheme, such as `https://`, and redacts the URL's password only.
111
+
112
+ A URL password is the text between `://user:` and the last `@` before the next `/` or space. The user can be empty, as in `redis://:password@cache`. Testkit does not recognise a password that holds a `/`, or a token that stands alone as the user, as in `https://token@host`.
113
+
114
+ Testkit does not rewrite a path or an area's name. `testkit bug record` rejects a record whose `test.file` or `fault.path` holds text that Testkit redacts. `testkit bug file` stops when the area's name or the reproduce command holds such text.
115
+
116
+ Testkit reads the facts back from the body before it files the issue. It files nothing when they do not read back as written. That happens when `test.title`, `test.file`, `fault.path`, or the area's name holds one of the two marker comments.
117
+
118
+ ## Facts block
119
+
120
+ The facts block is one JSON object in a fenced `json` code block between the two marker comments.
121
+
122
+ To read it:
123
+
124
+ 1. Find the last `<!-- testkit-facts:start -->` in the body.
125
+ 2. Find the first `<!-- testkit-facts:end -->` after it.
126
+ 3. Take the first fenced code block between the two markers.
127
+ 4. Parse its content as JSON. Accept `\r\n` line endings.
128
+
129
+ A body with no start marker has no facts. A body with a start marker and no readable object has invalid facts. A reader keeps the issue in both cases and records which case applies.
130
+
131
+ All paths in the block use `/` and are relative to the repository root.
132
+
133
+ | Field | Type | Written | Meaning |
134
+ | ------------------- | -------------------------------------------------------- | ------------------------------------- | --------------------------------------------------------------- |
135
+ | `v` | `1` | Always | The format version. |
136
+ | `cause` | Cause id | Always | The kind of mistake in the code. |
137
+ | `impact` | Impact id | Always | What is at stake for the business. |
138
+ | `confidence.cause` | `high` or `low` | Always | The finder's confidence in `cause`. |
139
+ | `confidence.impact` | `high` or `low` | Always | The finder's confidence in `impact`. |
140
+ | `escape` | Escape id | Always | Why the existing tests did not catch the bug. |
141
+ | `caughtBy` | `campaign` or `manual` | Always | `campaign` when a campaign was active at record time. |
142
+ | `campaign` | Campaign id | With `caughtBy: "campaign"` | The campaign that caught the bug. |
143
+ | `testType` | `ui`, `e2e`, `int`, `contract`, `data`, `dal`, or `load` | Always | The type of the regression test, from its file suffix. |
144
+ | `test.file` | Path | Always | The regression test file. |
145
+ | `test.title` | String | When the issue covers one test | The failure's title. See [Known failures](#known-failures). |
146
+ | `foundAt` | Full commit id | Always | The commit the test failed at. |
147
+ | `fault.path` | Path | Always | The source file that holds the mistake. |
148
+ | `fault.lines` | `[start, end]` | Always | The first and last faulty line at `foundAt`, counted from 1. |
149
+ | `area.id` | Area id | When the area map claims `fault.path` | The area's key in `areas`. |
150
+ | `area.name` | String | With `area.id` | The area's name at filing time. |
151
+ | `area.share` | Number from 0 to 1 | With `area.id` | The area's share of the repository's source bytes at `foundAt`. |
152
+ | `introducedBy` | Full commit id | When the blame resolves | The newest commit among the fault lines at `foundAt`. |
153
+ | `introducedMethod` | `blame` | With `introducedBy` | How Testkit found `introducedBy`. |
154
+
155
+ Testkit writes every field it can resolve. A reader accepts a version 1 block that lacks `escape`, `campaign`, `test.title`, `fault`, `fault.lines`, `area`, `introducedBy`, or `introducedMethod`, because issues that predate the CLI were given facts by hand. A reader ignores fields it does not know.
156
+
157
+ A reader rejects a block whose `v` it does not know, and a block where a field that is present has the wrong type or an id outside its list.
158
+
159
+ ## Cause
160
+
161
+ The question is what kind of mistake is in the code. A bug gets exactly one cause. When a bug holds several mistakes, classify the earliest one in the causal chain. Use `other` when none fits.
162
+
163
+ | Id | Definition | Boundary rule |
164
+ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
165
+ | `missing-validation` | The code never checks an input or a state precondition that the domain rules require: range, format, existence, uniqueness, cross-field consistency, or lifecycle state. Includes a validator that exists and accepts invalid input. | Earliest mistake wins. If application code never tested the condition, it is `missing-validation` even when a database constraint or the runtime later throws and the caller sees a server error. If the unchecked value is null, absent, empty, zero, or a default, use `null-or-default-mishandling`. |
166
+ | `mishandled-error` | A failure was detected and signalled, and the handling is wrong: not caught, translated to the wrong error, swallowed, ignored by the UI, or followed by a wrong retry or recovery. | The failure must already be signalled by something that checked. If nothing in the application checked and a lower layer failed, it is `missing-validation`. |
167
+ | `race-condition` | Correctness depends on how overlapping operations interleave: check-then-act without a lock or constraint, a lost update, or a late asynchronous completion that overwrites newer state. | Needs two overlapping operations to reproduce. If one sequential flow reproduces it, it is `stale-state` or `missing-validation`. |
168
+ | `unbounded-or-inefficient-work` | Results are correct and the cost is unbounded or needlessly repeated: unpaginated reads, N+1 queries, a missing or redundant index, serial waits, or loading everything and then slicing. | Correct results at excessive cost. If the result is wrong, it is `incorrect-query-or-selection`. |
169
+ | `incorrect-query-or-selection` | The code identifies the wrong set of records: a missing or wrong predicate, unstable ordering, a truncated page treated as complete, duplicate rows from a join, or a lookup key that omits a distinguishing part. | A missing predicate is `missing-access-control` only when it separates actors or tenants with different rights. The right records with wrong arithmetic on them is `incorrect-logic`. |
170
+ | `missing-access-control` | A permission, tenant, ownership, or separation-of-duties check is absent or wrong. | Use it when the missing check is about who the actor is. A missing check about what the data or state is belongs in `missing-validation`. |
171
+ | `null-or-default-mishandling` | Null, absent, empty, zero, or default values are conflated or not handled: null treated as omitted, a zero or an empty collection treated as missing, a null dereference, a wrong fallback default, or a caller that omits an argument and silently gets the default. | Wins over `missing-validation` and `incorrect-logic` whenever the triggering value is null, absent, empty, zero, or a default. |
172
+ | `stale-state` | Held state, such as a cache, local UI state, a derived or denormalised value, or leftover fields, is not refreshed, reset, persisted, or tied to the right identity when the thing it mirrors changes. | If the omission leaves old values visible where current ones should be, it is `stale-state`. If the missing action leaves nothing stale, it is `omitted-step`. If it reproduces only with overlapping operations, it is `race-condition`. |
173
+ | `interface-mismatch` | Data is lost or misread where it crosses between representations or components. One component's mapping drops, overwrites, hardcodes, or mis-assigns fields, or two components that must agree disagree on shape, type, field names, allowed values, semantics, or which copy of the data is authoritative. | Use it when the values were right before the crossing. Wrong computation on values that arrived intact is `incorrect-logic`. A held copy that goes out of date after its source changes is `stale-state`. An omitted argument that falls back to a default is `null-or-default-mishandling`. |
174
+ | `omitted-step` | A required follow-on action is not performed on some path: an audit record, a notification, a dispatch, a finalisation, or an alternate route that bypasses the shared routine. | If the missing thing is a check, it is `missing-validation`. If its absence leaves old values on display, it is `stale-state`. |
175
+ | `incorrect-logic` | The algorithm, arithmetic, date or time handling, rounding, boundary, comparison, boolean condition, parsing, or order of operations is wrong. | The default for wrong logic on valid, present values. If the trigger is a null, empty, zero, or default value, use `null-or-default-mishandling`. If the fault is which records were chosen, use `incorrect-query-or-selection`. |
176
+ | `other` | No category above describes the mistake. | |
177
+
178
+ ## Impact
179
+
180
+ The question is what is at stake for the business. A bug gets exactly one impact. The list is in precedence order, and the first type that applies wins. Classify the impact the test demonstrated, not a downstream one you expect. Use `other` when none fits.
181
+
182
+ | Id | Definition | Boundary rule |
183
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
184
+ | `unauthorized-access` | Someone can see or change something they are not entitled to, including getting past a separation-of-duties rule. | The actor lacks the right. If an entitled actor skips a mandated control or leaves no evidence, it is `audit-or-compliance-gap`. |
185
+ | `financial-error` | A monetary amount, rate, currency, limit, payment or settlement state, or a money-committing decision is wrong where it is stored, charged, paid, or decided. | A money figure that is only displayed wrongly, with the stored value and the decision intact, is `wrong-information-shown`. |
186
+ | `audit-or-compliance-gap` | Required audit evidence or attribution is missing, duplicated, or false, or a mandated control did not run. | Use it when the harm is to evidence or to a regulatory or policy control. A skipped ordinary process step is `workflow-breakdown`. |
187
+ | `data-loss-or-corruption` | Stored data, or work the user entered, is destroyed, overwritten, duplicated, or left invalid or inconsistent. | If the action silently did nothing and prior stored state is intact, it is `blocked-operation`. |
188
+ | `workflow-breakdown` | A business process, such as a workflow, task, schedule, notification, or gate, is skipped, stalled, duplicated, or advanced wrongly. | Use it when the process goes wrong after the triggering request returns. A single user action that fails in front of the user is `blocked-operation`. |
189
+ | `availability-or-performance` | The service is slow, unavailable, or exhausts resources in a way that affects more than the triggering request or grows with data volume. | One request that fails for one user is `blocked-operation`. |
190
+ | `blocked-operation` | A legitimate action fails, returns an unusable or misleading error, or silently does not apply, with prior stored state intact. | The user was trying to do something and could not. If they were only reading and what they read was wrong, it is `wrong-information-shown`. |
191
+ | `wrong-information-shown` | Displayed or returned information is wrong, out of date, incomplete, or duplicated, with nothing stored wrongly. | Last in precedence. If the wrong value is also persisted, it is `data-loss-or-corruption`. |
192
+ | `other` | No type above describes what is at stake. | |
193
+
194
+ ## Escape reason
195
+
196
+ The question is why the tests that existed at `foundAt` did not catch the bug. A bug gets exactly one reason. Judge the tests that were there before yours. Use `other` when none fits.
197
+
198
+ | Id | Definition | Boundary rule |
199
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
200
+ | `no-test` | No existing test runs the faulty behaviour. | If any existing test reaches the faulty code path, use another reason. |
201
+ | `untested-input` | A test runs the behaviour and never with the input, state, or data volume that triggers the fault. | One sequential flow with the right input reproduces the bug. If it needs overlapping operations, it is `untested-interleaving`. |
202
+ | `untested-interleaving` | A test runs the behaviour in sequence, and the fault needs overlapping operations or a particular timing. | The triggering input is already covered, and only the overlap is missing. |
203
+ | `wrong-assertion` | A test runs the triggering case against the real code and passes, because its assertion is missing, too weak, or asserts the broken behaviour. | The faulty code ran in the test. If a stand-in ran in its place, it is `test-double`. |
204
+ | `test-double` | A test runs the triggering case against a test double or a fixture that hides the fault in the real component. | The real component would have failed the test. |
205
+ | `other` | No reason above applies. | |
206
+
207
+ ## Areas
208
+
209
+ An area id matches `^[a-z0-9]+(?:-[a-z0-9]+)*$` and is at most 40 characters long.
210
+
211
+ `area.share` is the area's source bytes divided by the repository's source bytes at `foundAt`. A source file is a tracked file with a source extension that is not a test, a migration, a dependency, build output, a `.d.ts`, `.d.cts`, or `.d.mts` declaration file, a file with `.min.` in its name, a dotfile, or a `*.config.*` file at the repository root. Each source file counts toward one area at most. A file that two areas claim counts toward neither until the map is fixed.
212
+
213
+ ## Known failures
214
+
215
+ A failed file reports its failures in one of three forms.
216
+
217
+ | Failure | When |
218
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
219
+ | Named failure | The engine names the test that failed. One failure for each title. |
220
+ | Script failure | A k6 script fails as a whole: it calls `fail()`, throws while it runs, fails a k6 `check()`, or fails a threshold. One per file. |
221
+ | Unnamed failure | The file fails any other way, such as a crash, a timeout, a file that does not load, a Vitest error outside a test, a Playwright error outside a spec, or a k6 script that throws during `setup`. |
222
+
223
+ A named failure's title comes from the engine.
224
+
225
+ | Engine | Title |
226
+ | ---------- | -------------------------------------------------------------------------------------------------------------------------------- |
227
+ | Vitest | The names of the `describe` blocks around the test and the test's own name, joined by single spaces. |
228
+ | Playwright | The names of the `describe` blocks around the spec and the spec's own title, joined by single spaces. |
229
+ | k6 | The label of a failed assertion from `@gfdelarue/testkit/k6`, or the assertion's request and default title when it has no label. |
230
+
231
+ A Playwright title holds neither the file path nor the project name. A spec that fails in two projects is one named failure.
232
+
233
+ A k6 assertion on a response has a default title when the script gives it no label. Those assertions are `expect.status`, `expect.statusOneOf`, `expect.notStatus`, `expect.json`, `expect.jsonPath`, `expect.error.shape`, and `expect.error.message`. Testkit names such a failure by its request and the default title, as in `GET /health > status is 201`. Inside a k6 `group`, the group's name comes first, as in `orders > GET /health > status is 201`. Any other assertion with no label keeps its default title, such as `row count is 2`. A label names its failure as written, even when it reads like a default title. Two failures in one file whose titles are equal are one named failure.
234
+
235
+ When a file reports named failures, `testkit bug file` rejects a bug record whose `test.title` is not one of their titles. Its message lists the titles a bug record can use.
236
+
237
+ Testkit files no bug under a title that spans lines or holds text it redacts as a secret, so no issue with a `test.title` covers such a failure. `testkit run` redacts some secrets when it stores a run result and writes `<redacted>` in their place. A title that the run result stores with `<redacted>` is such a title. Rename the test, or give the assertion a label, so that its title is one line and holds no such text. [Issue body](#issue-body) lists what Testkit redacts.
238
+
239
+ Vitest reports an error outside a test when a hook such as `beforeAll` throws, or when code throws after its test has returned. The file then reports one unnamed failure beside its named failures.
240
+
241
+ Playwright reports an error outside a spec when code such as a `globalTeardown` throws. The file then reports one unnamed failure beside its named failures.
242
+
243
+ A file also reports one unnamed failure beside its named failures when the engine fails the file for a reason that its failed tests do not explain.
244
+
245
+ - A Vitest file runs past its timeout, writes more output than Testkit keeps, or ends on a signal or with an exit code other than 1.
246
+ - A Playwright run is interrupted or runs past its timeout.
247
+ - A Playwright process ends on a signal or with an exit code other than 1. A reporter in the Playwright config that ends the process does that.
248
+ - A Playwright config sets `failOnFlakyTests` and a spec passes only on a retry. A spec that passes on a retry is no named failure, so no issue can cover it.
249
+ - A Playwright spec fails in a file other than the selected one, such as a spec in a project that the selected file's project depends on or a spec in a teardown project. That spec is no named failure of the selected file, so no issue can cover it. A spec's file is the file that Playwright loaded. A compiled file whose source map names the selected file is still another file.
250
+ - Testkit cannot keep a file that a Playwright spec attached, or cannot store the evidence of the Playwright run.
251
+ - A k6 process whose thresholds failed ends on a signal or with an exit code other than 99.
252
+ - A k6 process whose script threw an error ends on a signal or with an exit code other than 0 or 99, or leaves no summary that Testkit can read.
253
+
254
+ A k6 script reports a script failure and no named failure when it fails only through `fail()`, a thrown error, a k6 `check()`, or a threshold. `testkit bug file` then files one issue for the whole file, and the issue's facts carry no `test.title`.
255
+
256
+ A k6 script reports its named failures and one script failure when it also fails that way beside the assertions the run names. Testkit tells a k6 `check()` from its own assertions by count. The k6 summary counts every failed check, and each failed assertion from `@gfdelarue/testkit/k6` is one failed check. When the summary's count differs from the number of failed assertions the run names, or the summary carries no count, the file has a script failure.
257
+
258
+ A failure is known when an open issue with the `testkit` label covers it. The issue's `test.file` must equal the failing file's path from the repository root.
259
+
260
+ | Failure | Covered by |
261
+ | --------------- | ----------------------------------------------------------------------------------------------------- |
262
+ | Named failure | An issue whose `test.title` equals the failure's title, or an issue whose facts have no `test.title`. |
263
+ | Script failure | An issue whose facts have no `test.title`. |
264
+ | Unnamed failure | No issue. |
265
+
266
+ An issue with a `test.title` covers that one title and nothing else. An issue with no `test.title` covers the whole file. That is every named failure in the file and its script failure, and never an unnamed failure. An unnamed failure is always new. Closing an issue makes the next failure it covered new.
267
+
268
+ When Testkit cannot read the issues, every failure is new.
269
+
270
+ `bugs.knownFailures` in `testkit.config.ts` decides what a known failure does to a run. With `"fail"`, the default, every failing test fails the run. With `"allow"`, a run whose failures are all known passes, and one new failure fails it. A run that fails outside its tests fails under both settings.
271
+
272
+ ## Commit status
273
+
274
+ `testkit ci report` posts one commit status on the checked-out commit. When CI checks out a pull request's merge commit, the status goes on the pull request's head commit instead.
275
+
276
+ | Part | Value |
277
+ | ----------- | ------------------------------------------------------------------------------------------------------------------------- |
278
+ | Context | `testkit`, or the value of `--context`. |
279
+ | State | `success` when the run passed, or when `bugs.knownFailures` is `"allow"` and every failure is known. `failure` otherwise. |
280
+ | Target URL | The CI run's URL, when the CI provider gives one. |
281
+ | Description | See below. At most 140 characters. |
282
+
283
+ The description has one of three forms.
284
+
285
+ | Run | Description |
286
+ | -------------------------------- | -------------------------- |
287
+ | Every file passed | `142 files passed` |
288
+ | Tests failed | `3 failed: 1 new, 2 known` |
289
+ | The run failed outside its tests | `run failed: <reason>` |
290
+
291
+ The counts are failures. A script failure counts as one failure. An unnamed failure counts as one new failure.
292
+
293
+ Testkit redacts the secrets it recognises in the reason. [Issue body](#issue-body) lists the forms.
294
+
295
+ `testkit run` redacts some secrets when it stores the run result, and that redaction can stop before the secret's end. The description therefore ends at the first `<redacted>` in the stored reason. A redacted URL password is the exception. `testkit run` redacts a URL password whole, so the description continues past it.
296
+
297
+ ## Pull request comment
298
+
299
+ `testkit ci report` keeps one comment on the run's pull request and updates it in place. The comment's first line is `<!-- testkit-ci-report -->`, or `<!-- testkit-ci-report:<context> -->` when the context is not `testkit`. Testkit finds its comment by that line.
300
+
301
+ The comment names each failure by its file and its title. Testkit redacts the secrets it recognises in both, in the reason for a run that failed outside its tests, and in the reason it could not read the open bugs. [Issue body](#issue-body) lists the forms.
302
+
303
+ A file, a title, or a run's reason that the run result stores with `<redacted>` ends there in the comment and in the step summary, as it does in the [commit status](#commit-status). A title such as `rejects PASSWORD=<redacted> with 401` shows as `rejects PASSWORD=<redacted>`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gfdelarue/testkit",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Test orchestration platform for product acceptance and QA workflows",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -86,6 +86,7 @@
86
86
  "files": [
87
87
  "bin/",
88
88
  "dist/",
89
+ "docs/bug-records.md",
89
90
  "docs/engine-architecture.md",
90
91
  "docs/test-types.md",
91
92
  "testkit-config-schema.md"
@@ -156,6 +156,56 @@ Optional `testkit lint` settings. Omit it to run the default rules.
156
156
  Enabled-rule findings and syntax errors fail the command. There are no severity
157
157
  levels. The repository's `docs/lint.md` describes each rule.
158
158
 
159
+ ### `areas`
160
+
161
+ Optional map of the product's areas. `testkit bug file` uses it to name the area
162
+ that holds a fault, and puts an `area:<id>` label on the issue.
163
+
164
+ ```ts
165
+ areas: {
166
+ checkout: { name: "Checkout", paths: ["src/checkout/**"] },
167
+ billing: { name: "Billing", paths: ["src/billing/**", "src/invoices/**"] },
168
+ },
169
+ ```
170
+
171
+ - The key is the area id: lowercase letters, digits, and single hyphens, at
172
+ most 40 characters.
173
+ - `name`: one line of 1 to 60 characters. It becomes the label's description.
174
+ - `paths`: at least one glob. Globs are relative to the repository root and
175
+ written with `/`. A leading `!` or `/` and a `..` segment are rejected.
176
+
177
+ Each source file belongs to at most one area. `testkit campaign start` lists
178
+ the source files that no area claims and the files that two areas claim.
179
+ `testkit bug file` stops when no area claims the fault's file, or when two
180
+ areas do, and names the file. Without `areas`, Testkit files bugs with no area.
181
+
182
+ An area's share is its part of the repository's source bytes at the commit that
183
+ holds the bug. Tests, generated files, vendored code, and migrations do not
184
+ count as source.
185
+
186
+ ### `bugs`
187
+
188
+ Optional settings for bug filing and for runs that meet a filed bug.
189
+
190
+ - `knownFailures`: `"fail"` (default) or `"allow"`. A known failure is a
191
+ failing test that an open issue with the `testkit` label covers.
192
+ [Known failures](docs/bug-records.md#known-failures) gives the rule. With
193
+ `"fail"`, every failing test fails the run. With `"allow"`, a run passes when
194
+ every failure is known, and only a new failure fails it. Closing the issue
195
+ makes the test count again. When Testkit cannot reach the tracker, it treats
196
+ every failure as new. With `"allow"`, the run reads the open issues, so the
197
+ `testkit run` step in CI needs a token.
198
+ [Pass a run whose failures are all filed](README.md#pass-a-run-whose-failures-are-all-filed)
199
+ shows the step.
200
+ - `repository`: the repository that holds the bug issues. `testkit bug file`
201
+ files issues there, and `testkit run` and `testkit ci report` read the open
202
+ issues from it. Commit statuses and pull request comments always go to the
203
+ `origin` repository. Write `owner/name` for a repository on the same host as
204
+ the `origin` remote, or a URL such as `https://host/owner/name`. It defaults
205
+ to the `origin` remote.
206
+
207
+ [Bug records](docs/bug-records.md) describes the issues Testkit files.
208
+
159
209
  ### `ui`
160
210
 
161
211
  Targets for Playwright `ui` files. Read through `createUiSandbox`,