@elench/testkit 0.1.83 → 0.1.84
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/lib/cli/agents/providers/codex.mjs +1 -1
- package/lib/cli/tui/run-session-app.mjs +1 -1
- package/node_modules/@elench/next-analysis/package.json +1 -1
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/ts-analysis/package.json +1 -1
- package/package.json +7 -6
- package/lib/app/configs.test.mjs +0 -34
- package/lib/app/typecheck.test.mjs +0 -24
- package/lib/bundler/index.test.mjs +0 -164
- package/lib/cli/agents/investigation-context.test.mjs +0 -144
- package/lib/cli/agents/providers/claude.test.mjs +0 -95
- package/lib/cli/agents/providers/codex.test.mjs +0 -93
- package/lib/cli/args.test.mjs +0 -110
- package/lib/cli/command-helpers.test.mjs +0 -122
- package/lib/cli/commands/investigate.test.mjs +0 -83
- package/lib/cli/presentation/code-frames.test.mjs +0 -71
- package/lib/cli/presentation/events-reporter.test.mjs +0 -73
- package/lib/cli/presentation/run-reporter.test.mjs +0 -192
- package/lib/cli/presentation/summary-box.test.mjs +0 -60
- package/lib/cli/presentation/terminal-layout.test.mjs +0 -23
- package/lib/cli/presentation/tree-reporter.test.mjs +0 -166
- package/lib/cli/tui/run-session-app.test.mjs +0 -50
- package/lib/cli/tui/run-tree-state.test.mjs +0 -324
- package/lib/config/database.test.mjs +0 -29
- package/lib/config/discovery.test.mjs +0 -276
- package/lib/config/env.test.mjs +0 -40
- package/lib/config/index.test.mjs +0 -44
- package/lib/config/paths.test.mjs +0 -27
- package/lib/config/runtime.test.mjs +0 -82
- package/lib/config/skip-config.test.mjs +0 -63
- package/lib/config-api/index.test.mjs +0 -398
- package/lib/config-api/next-runtime-tsconfig.test.mjs +0 -58
- package/lib/coverage/backend-discovery.test.mjs +0 -61
- package/lib/coverage/evidence.test.mjs +0 -87
- package/lib/coverage/index.test.mjs +0 -715
- package/lib/coverage/routing.test.mjs +0 -36
- package/lib/coverage/shared.test.mjs +0 -72
- package/lib/database/fingerprint.test.mjs +0 -99
- package/lib/database/index.test.mjs +0 -95
- package/lib/database/naming.test.mjs +0 -39
- package/lib/database/state.test.mjs +0 -66
- package/lib/database/template-steps.test.mjs +0 -43
- package/lib/discovery/file-metadata.test.mjs +0 -51
- package/lib/discovery/index.test.mjs +0 -182
- package/lib/discovery/path-policy.test.mjs +0 -65
- package/lib/drizzle/index.test.mjs +0 -33
- package/lib/env/index.test.mjs +0 -82
- package/lib/history/index.test.mjs +0 -115
- package/lib/package.test.mjs +0 -59
- package/lib/playwright/index.test.mjs +0 -43
- package/lib/regressions/github.test.mjs +0 -324
- package/lib/regressions/index.test.mjs +0 -187
- package/lib/reporters/playwright.test.mjs +0 -167
- package/lib/runner/default-runtime-errors.test.mjs +0 -49
- package/lib/runner/execution-config.test.mjs +0 -67
- package/lib/runner/failure-details.test.mjs +0 -114
- package/lib/runner/formatting.test.mjs +0 -205
- package/lib/runner/metadata.test.mjs +0 -52
- package/lib/runner/planning.test.mjs +0 -371
- package/lib/runner/playwright-config.test.mjs +0 -78
- package/lib/runner/processes.test.mjs +0 -21
- package/lib/runner/regressions.test.mjs +0 -168
- package/lib/runner/reporting.test.mjs +0 -310
- package/lib/runner/results.test.mjs +0 -376
- package/lib/runner/runtime-manager.test.mjs +0 -252
- package/lib/runner/runtime-preparation.test.mjs +0 -141
- package/lib/runner/selection.test.mjs +0 -24
- package/lib/runner/setup-operations.test.mjs +0 -94
- package/lib/runner/state.test.mjs +0 -62
- package/lib/runner/suite-selection.test.mjs +0 -49
- package/lib/runner/template.test.mjs +0 -272
- package/lib/runtime-src/k6/http-checks.test.mjs +0 -120
- package/lib/runtime-src/k6/http.test.mjs +0 -205
- package/lib/runtime-src/shared/http-parsing.test.mjs +0 -69
- package/lib/shared/build-config.test.mjs +0 -132
- package/lib/shared/configured-steps.test.mjs +0 -102
- package/lib/shared/execution-schema.test.mjs +0 -26
- package/lib/shared/file-timeout.test.mjs +0 -64
- package/lib/shared/test-context.test.mjs +0 -43
- package/lib/timing/index.test.mjs +0 -64
- package/lib/toolchains/index.test.mjs +0 -168
- package/lib/vitest/index.test.mjs +0 -20
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import os from "os";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
import { normalizeRegressionCatalogDocument } from "./index.mjs";
|
|
6
|
-
import {
|
|
7
|
-
parseGitHubRepoSlug,
|
|
8
|
-
shouldFailRegressionSync,
|
|
9
|
-
validateRegressionIssues,
|
|
10
|
-
} from "./github.mjs";
|
|
11
|
-
|
|
12
|
-
const tempDirs = [];
|
|
13
|
-
|
|
14
|
-
afterEach(() => {
|
|
15
|
-
for (const tempDir of tempDirs.splice(0)) {
|
|
16
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
describe("regression GitHub sync", () => {
|
|
21
|
-
it("parses GitHub repo slugs from common origin URLs", () => {
|
|
22
|
-
expect(parseGitHubRepoSlug("https://github.com/acme/repo.git")).toBe("acme/repo");
|
|
23
|
-
expect(parseGitHubRepoSlug("git@github.com:acme/repo.git")).toBe("acme/repo");
|
|
24
|
-
expect(parseGitHubRepoSlug("ssh://git@github.com/acme/repo.git")).toBe("acme/repo");
|
|
25
|
-
expect(parseGitHubRepoSlug("https://gitlab.com/acme/repo.git")).toBe(null);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("fails sync when a linked issue is closed but the regression still reproduces", async () => {
|
|
29
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-regressions-gh-"));
|
|
30
|
-
tempDirs.push(tempDir);
|
|
31
|
-
const document = normalizeRegressionCatalogDocument({
|
|
32
|
-
schemaVersion: 1,
|
|
33
|
-
issueRepo: "acme/repo",
|
|
34
|
-
entries: [
|
|
35
|
-
{
|
|
36
|
-
id: "bad-message",
|
|
37
|
-
classification: "product_bug",
|
|
38
|
-
issue: {
|
|
39
|
-
repo: "acme/repo",
|
|
40
|
-
number: 12,
|
|
41
|
-
},
|
|
42
|
-
summary: "API returns the wrong message",
|
|
43
|
-
cause: "The payload is wrong.",
|
|
44
|
-
lastReviewedAt: "2026-05-04",
|
|
45
|
-
fingerprints: [
|
|
46
|
-
{
|
|
47
|
-
service: "api",
|
|
48
|
-
type: "int",
|
|
49
|
-
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
50
|
-
},
|
|
51
|
-
],
|
|
52
|
-
},
|
|
53
|
-
],
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const result = await validateRegressionIssues({
|
|
57
|
-
productDir: tempDir,
|
|
58
|
-
document,
|
|
59
|
-
statusArtifact: {
|
|
60
|
-
tests: [
|
|
61
|
-
{
|
|
62
|
-
service: "api",
|
|
63
|
-
type: "int",
|
|
64
|
-
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
65
|
-
status: "failed",
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
},
|
|
69
|
-
config: {
|
|
70
|
-
provider: "github",
|
|
71
|
-
mode: "error",
|
|
72
|
-
cacheTtlSeconds: 60,
|
|
73
|
-
},
|
|
74
|
-
gitMetadata: {
|
|
75
|
-
repoSlug: "acme/repo",
|
|
76
|
-
remoteUrl: "https://github.com/acme/repo.git",
|
|
77
|
-
},
|
|
78
|
-
transport: {
|
|
79
|
-
async fetchRepoIssues(repo, numbers) {
|
|
80
|
-
const map = new Map();
|
|
81
|
-
map.set(numbers[0], {
|
|
82
|
-
repo,
|
|
83
|
-
number: numbers[0],
|
|
84
|
-
exists: true,
|
|
85
|
-
title: "API returns the wrong message",
|
|
86
|
-
state: "CLOSED",
|
|
87
|
-
url: `https://github.com/${repo}/issues/${numbers[0]}`,
|
|
88
|
-
checkedAt: "2026-05-04T00:00:00.000Z",
|
|
89
|
-
source: "github",
|
|
90
|
-
});
|
|
91
|
-
return map;
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
expect(result.summary.byCode.closed_but_failing).toBe(1);
|
|
97
|
-
expect(result.summary.errors).toBeGreaterThan(0);
|
|
98
|
-
expect(result.entries[0].status).toBe("closed_but_failing");
|
|
99
|
-
expect(shouldFailRegressionSync(result)).toBe(true);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("allows multiple local entries to share one GitHub issue", async () => {
|
|
103
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-regressions-gh-shared-"));
|
|
104
|
-
tempDirs.push(tempDir);
|
|
105
|
-
const document = normalizeRegressionCatalogDocument({
|
|
106
|
-
schemaVersion: 1,
|
|
107
|
-
issueRepo: "acme/repo",
|
|
108
|
-
entries: [
|
|
109
|
-
{
|
|
110
|
-
id: "route-a-invalid-uuid",
|
|
111
|
-
classification: "product_bug",
|
|
112
|
-
issue: {
|
|
113
|
-
repo: "acme/repo",
|
|
114
|
-
number: 77,
|
|
115
|
-
},
|
|
116
|
-
summary: "Route A leaks UUID parse errors",
|
|
117
|
-
cause: "Route A is missing UUID validation.",
|
|
118
|
-
lastReviewedAt: "2026-05-04",
|
|
119
|
-
fingerprints: [
|
|
120
|
-
{
|
|
121
|
-
service: "api",
|
|
122
|
-
type: "int",
|
|
123
|
-
path: "src/api/routes/__testkit__/route-a.int.testkit.ts",
|
|
124
|
-
},
|
|
125
|
-
],
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
id: "route-b-invalid-uuid",
|
|
129
|
-
classification: "product_bug",
|
|
130
|
-
issue: {
|
|
131
|
-
repo: "acme/repo",
|
|
132
|
-
number: 77,
|
|
133
|
-
},
|
|
134
|
-
summary: "Route B leaks UUID parse errors",
|
|
135
|
-
cause: "Route B is missing UUID validation.",
|
|
136
|
-
lastReviewedAt: "2026-05-04",
|
|
137
|
-
fingerprints: [
|
|
138
|
-
{
|
|
139
|
-
service: "api",
|
|
140
|
-
type: "int",
|
|
141
|
-
path: "src/api/routes/__testkit__/route-b.int.testkit.ts",
|
|
142
|
-
},
|
|
143
|
-
],
|
|
144
|
-
},
|
|
145
|
-
],
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
const result = await validateRegressionIssues({
|
|
149
|
-
productDir: tempDir,
|
|
150
|
-
document,
|
|
151
|
-
statusArtifact: {
|
|
152
|
-
tests: [
|
|
153
|
-
{
|
|
154
|
-
service: "api",
|
|
155
|
-
type: "int",
|
|
156
|
-
path: "src/api/routes/__testkit__/route-a.int.testkit.ts",
|
|
157
|
-
status: "failed",
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
service: "api",
|
|
161
|
-
type: "int",
|
|
162
|
-
path: "src/api/routes/__testkit__/route-b.int.testkit.ts",
|
|
163
|
-
status: "failed",
|
|
164
|
-
},
|
|
165
|
-
],
|
|
166
|
-
},
|
|
167
|
-
config: {
|
|
168
|
-
provider: "github",
|
|
169
|
-
mode: "error",
|
|
170
|
-
},
|
|
171
|
-
transport: {
|
|
172
|
-
async fetchRepoIssues(repo, numbers) {
|
|
173
|
-
const map = new Map();
|
|
174
|
-
map.set(numbers[0], {
|
|
175
|
-
repo,
|
|
176
|
-
number: numbers[0],
|
|
177
|
-
exists: true,
|
|
178
|
-
title: "UUID path params reach Postgres and return 500 instead of 400",
|
|
179
|
-
state: "OPEN",
|
|
180
|
-
url: `https://github.com/${repo}/issues/${numbers[0]}`,
|
|
181
|
-
checkedAt: "2026-05-04T00:00:00.000Z",
|
|
182
|
-
source: "github",
|
|
183
|
-
});
|
|
184
|
-
return map;
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
expect(result.summary.errors).toBe(0);
|
|
190
|
-
expect(result.entries).toHaveLength(2);
|
|
191
|
-
expect(result.entries[0].findings).toEqual([]);
|
|
192
|
-
expect(result.entries[1].findings).toEqual([]);
|
|
193
|
-
expect(result.entries[0].status).toBe("open_and_failing");
|
|
194
|
-
expect(result.entries[1].status).toBe("open_and_failing");
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it("warns when an open issue is not reproduced", async () => {
|
|
198
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-regressions-gh-open-"));
|
|
199
|
-
tempDirs.push(tempDir);
|
|
200
|
-
const document = normalizeRegressionCatalogDocument({
|
|
201
|
-
schemaVersion: 1,
|
|
202
|
-
issueRepo: "acme/repo",
|
|
203
|
-
entries: [
|
|
204
|
-
{
|
|
205
|
-
id: "bad-message",
|
|
206
|
-
classification: "product_bug",
|
|
207
|
-
issue: {
|
|
208
|
-
repo: "acme/repo",
|
|
209
|
-
number: 12,
|
|
210
|
-
},
|
|
211
|
-
summary: "API returns the wrong message",
|
|
212
|
-
cause: "The payload is wrong.",
|
|
213
|
-
lastReviewedAt: "2026-05-04",
|
|
214
|
-
fingerprints: [
|
|
215
|
-
{
|
|
216
|
-
service: "api",
|
|
217
|
-
type: "int",
|
|
218
|
-
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
219
|
-
},
|
|
220
|
-
],
|
|
221
|
-
},
|
|
222
|
-
],
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
const result = await validateRegressionIssues({
|
|
226
|
-
productDir: tempDir,
|
|
227
|
-
document,
|
|
228
|
-
statusArtifact: {
|
|
229
|
-
tests: [
|
|
230
|
-
{
|
|
231
|
-
service: "api",
|
|
232
|
-
type: "int",
|
|
233
|
-
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
234
|
-
status: "passed",
|
|
235
|
-
},
|
|
236
|
-
],
|
|
237
|
-
},
|
|
238
|
-
config: {
|
|
239
|
-
provider: "github",
|
|
240
|
-
mode: "warn",
|
|
241
|
-
},
|
|
242
|
-
gitMetadata: {
|
|
243
|
-
repoSlug: "acme/repo",
|
|
244
|
-
},
|
|
245
|
-
transport: {
|
|
246
|
-
async fetchRepoIssues(repo, numbers) {
|
|
247
|
-
const map = new Map();
|
|
248
|
-
map.set(numbers[0], {
|
|
249
|
-
repo,
|
|
250
|
-
number: numbers[0],
|
|
251
|
-
exists: true,
|
|
252
|
-
title: "API returns the wrong message",
|
|
253
|
-
state: "OPEN",
|
|
254
|
-
url: `https://github.com/${repo}/issues/${numbers[0]}`,
|
|
255
|
-
checkedAt: "2026-05-04T00:00:00.000Z",
|
|
256
|
-
source: "github",
|
|
257
|
-
});
|
|
258
|
-
return map;
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
expect(result.summary.byCode.open_not_reproduced).toBe(1);
|
|
264
|
-
expect(result.summary.errors).toBe(0);
|
|
265
|
-
expect(result.summary.warnings).toBe(1);
|
|
266
|
-
expect(result.entries[0].status).toBe("open_not_reproduced");
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it("falls back to warning when sync is unavailable", async () => {
|
|
270
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-regressions-gh-unavailable-"));
|
|
271
|
-
tempDirs.push(tempDir);
|
|
272
|
-
const document = normalizeRegressionCatalogDocument({
|
|
273
|
-
schemaVersion: 1,
|
|
274
|
-
entries: [
|
|
275
|
-
{
|
|
276
|
-
id: "bad-message",
|
|
277
|
-
classification: "product_bug",
|
|
278
|
-
issue: {
|
|
279
|
-
repo: "acme/repo",
|
|
280
|
-
number: 12,
|
|
281
|
-
},
|
|
282
|
-
summary: "API returns the wrong message",
|
|
283
|
-
cause: "The payload is wrong.",
|
|
284
|
-
lastReviewedAt: "2026-05-04",
|
|
285
|
-
fingerprints: [
|
|
286
|
-
{
|
|
287
|
-
service: "api",
|
|
288
|
-
type: "int",
|
|
289
|
-
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
290
|
-
},
|
|
291
|
-
],
|
|
292
|
-
},
|
|
293
|
-
],
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
const result = await validateRegressionIssues({
|
|
297
|
-
productDir: tempDir,
|
|
298
|
-
document,
|
|
299
|
-
statusArtifact: {
|
|
300
|
-
tests: [
|
|
301
|
-
{
|
|
302
|
-
service: "api",
|
|
303
|
-
type: "int",
|
|
304
|
-
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
305
|
-
status: "failed",
|
|
306
|
-
},
|
|
307
|
-
],
|
|
308
|
-
},
|
|
309
|
-
config: {
|
|
310
|
-
provider: "github",
|
|
311
|
-
mode: "warn",
|
|
312
|
-
},
|
|
313
|
-
transport: {
|
|
314
|
-
async fetchRepoIssues() {
|
|
315
|
-
throw new Error("boom");
|
|
316
|
-
},
|
|
317
|
-
},
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
expect(result.summary.byCode.validation_unavailable).toBe(1);
|
|
321
|
-
expect(result.summary.errors).toBe(0);
|
|
322
|
-
expect(result.summary.warnings).toBe(1);
|
|
323
|
-
});
|
|
324
|
-
});
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import os from "os";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
-
import {
|
|
6
|
-
findMatchingRegressionEntries,
|
|
7
|
-
normalizeRegressionCatalogDocument,
|
|
8
|
-
renderRegressionCatalogMarkdown,
|
|
9
|
-
validateRegressionCatalogDocument,
|
|
10
|
-
} from "./index.mjs";
|
|
11
|
-
|
|
12
|
-
const tempDirs = [];
|
|
13
|
-
|
|
14
|
-
afterEach(() => {
|
|
15
|
-
for (const tempDir of tempDirs.splice(0)) {
|
|
16
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
17
|
-
}
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
describe("regression catalog", () => {
|
|
21
|
-
it("matches entries by service, type, path, and failure key", () => {
|
|
22
|
-
const document = normalizeRegressionCatalogDocument({
|
|
23
|
-
schemaVersion: 1,
|
|
24
|
-
entries: [
|
|
25
|
-
{
|
|
26
|
-
id: "bad-message",
|
|
27
|
-
classification: "product_bug",
|
|
28
|
-
issue: {
|
|
29
|
-
repo: "acme/repo",
|
|
30
|
-
number: 12,
|
|
31
|
-
},
|
|
32
|
-
summary: "API returns the wrong message",
|
|
33
|
-
cause: "The payload is wrong.",
|
|
34
|
-
lastReviewedAt: "2026-05-04",
|
|
35
|
-
fingerprints: [
|
|
36
|
-
{
|
|
37
|
-
service: "api",
|
|
38
|
-
type: "int",
|
|
39
|
-
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
40
|
-
failureKey: "returns the wrong message",
|
|
41
|
-
},
|
|
42
|
-
],
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const matches = findMatchingRegressionEntries(document, {
|
|
48
|
-
service: "api",
|
|
49
|
-
type: "int",
|
|
50
|
-
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
51
|
-
error: "boom",
|
|
52
|
-
failureDetails: [{ key: "returns the wrong message" }],
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
expect(matches).toHaveLength(1);
|
|
56
|
-
expect(matches[0].id).toBe("bad-message");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("matches fingerprint failureKey against detail title", () => {
|
|
60
|
-
const document = normalizeRegressionCatalogDocument({
|
|
61
|
-
schemaVersion: 1,
|
|
62
|
-
entries: [
|
|
63
|
-
{
|
|
64
|
-
id: "missing-route",
|
|
65
|
-
classification: "product_bug",
|
|
66
|
-
issue: {
|
|
67
|
-
repo: "acme/repo",
|
|
68
|
-
number: 13,
|
|
69
|
-
},
|
|
70
|
-
summary: "Missing route returns the wrong status",
|
|
71
|
-
cause: "The route returns 404.",
|
|
72
|
-
lastReviewedAt: "2026-05-04",
|
|
73
|
-
fingerprints: [
|
|
74
|
-
{
|
|
75
|
-
service: "api",
|
|
76
|
-
type: "int",
|
|
77
|
-
path: "__testkit__/health/http-failure.int.testkit.ts",
|
|
78
|
-
failureKey: "GET /missing returns 200",
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
},
|
|
82
|
-
],
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
const matches = findMatchingRegressionEntries(document, {
|
|
86
|
-
service: "api",
|
|
87
|
-
type: "int",
|
|
88
|
-
path: "__testkit__/health/http-failure.int.testkit.ts",
|
|
89
|
-
error: "Default runtime thresholds failed: checks(rate==1.0)",
|
|
90
|
-
failureDetails: [
|
|
91
|
-
{
|
|
92
|
-
key: "GET /missing > GET /missing returns 200",
|
|
93
|
-
title: "GET /missing returns 200",
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
expect(matches).toHaveLength(1);
|
|
99
|
-
expect(matches[0].id).toBe("missing-route");
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("validates status coverage and filesystem fingerprints", () => {
|
|
103
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-regressions-"));
|
|
104
|
-
tempDirs.push(tempDir);
|
|
105
|
-
const testPath = path.join(tempDir, "src/api/routes/__testkit__/failing.int.testkit.ts");
|
|
106
|
-
fs.mkdirSync(path.dirname(testPath), { recursive: true });
|
|
107
|
-
fs.writeFileSync(testPath, "export default {};\n");
|
|
108
|
-
|
|
109
|
-
const document = normalizeRegressionCatalogDocument({
|
|
110
|
-
schemaVersion: 1,
|
|
111
|
-
issueRepo: "acme/repo",
|
|
112
|
-
entries: [
|
|
113
|
-
{
|
|
114
|
-
id: "bad-message",
|
|
115
|
-
classification: "product_bug",
|
|
116
|
-
issue: {
|
|
117
|
-
repo: "acme/repo",
|
|
118
|
-
number: 12,
|
|
119
|
-
},
|
|
120
|
-
summary: "API returns the wrong message",
|
|
121
|
-
cause: "The payload is wrong.",
|
|
122
|
-
lastReviewedAt: "2026-05-04",
|
|
123
|
-
fingerprints: [
|
|
124
|
-
{
|
|
125
|
-
service: "api",
|
|
126
|
-
type: "int",
|
|
127
|
-
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
128
|
-
},
|
|
129
|
-
],
|
|
130
|
-
},
|
|
131
|
-
],
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
const result = validateRegressionCatalogDocument(document, {
|
|
135
|
-
productDir: tempDir,
|
|
136
|
-
statusArtifact: {
|
|
137
|
-
tests: [
|
|
138
|
-
{
|
|
139
|
-
service: "api",
|
|
140
|
-
type: "int",
|
|
141
|
-
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
142
|
-
status: "failed",
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
service: "api",
|
|
146
|
-
type: "int",
|
|
147
|
-
path: "src/api/routes/__testkit__/other.int.testkit.ts",
|
|
148
|
-
status: "failed",
|
|
149
|
-
},
|
|
150
|
-
],
|
|
151
|
-
},
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
expect(result.errors).toEqual([]);
|
|
155
|
-
expect(result.stats.failedTests).toBe(2);
|
|
156
|
-
expect(result.stats.diagnosedFailedTests).toBe(1);
|
|
157
|
-
expect(result.warnings).toContain(
|
|
158
|
-
"New failing test not yet in regression catalog: src/api/routes/__testkit__/other.int.testkit.ts"
|
|
159
|
-
);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("renders markdown from the canonical document", () => {
|
|
163
|
-
const markdown = renderRegressionCatalogMarkdown(
|
|
164
|
-
normalizeRegressionCatalogDocument({
|
|
165
|
-
schemaVersion: 1,
|
|
166
|
-
entries: [
|
|
167
|
-
{
|
|
168
|
-
id: "bad-message",
|
|
169
|
-
classification: "product_bug",
|
|
170
|
-
issue: {
|
|
171
|
-
repo: "acme/repo",
|
|
172
|
-
number: 12,
|
|
173
|
-
},
|
|
174
|
-
summary: "API returns the wrong message",
|
|
175
|
-
cause: "The payload is wrong.",
|
|
176
|
-
lastReviewedAt: "2026-05-04",
|
|
177
|
-
fingerprints: [{ path: "src/api/routes/__testkit__/failing.int.testkit.ts" }],
|
|
178
|
-
},
|
|
179
|
-
],
|
|
180
|
-
})
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
expect(markdown).toContain("# Regression Catalog");
|
|
184
|
-
expect(markdown).toContain("API returns the wrong message");
|
|
185
|
-
expect(markdown).toContain("#12");
|
|
186
|
-
});
|
|
187
|
-
});
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
choosePlaywrightFinalResult,
|
|
4
|
-
extractPlaywrightFailure,
|
|
5
|
-
parsePlaywrightJsonResults,
|
|
6
|
-
} from "./playwright.mjs";
|
|
7
|
-
|
|
8
|
-
describe("playwright-report", () => {
|
|
9
|
-
it("handles empty and invalid output", () => {
|
|
10
|
-
expect(parsePlaywrightJsonResults("", "/tmp")).toEqual({
|
|
11
|
-
fileResults: new Map(),
|
|
12
|
-
errors: [],
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
const invalid = parsePlaywrightJsonResults("{", "/tmp");
|
|
16
|
-
expect(invalid.errors[0]).toContain("Could not parse Playwright JSON output");
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("collects nested suite results", () => {
|
|
20
|
-
const stdout = JSON.stringify({
|
|
21
|
-
errors: [{ message: "reporter failure" }],
|
|
22
|
-
suites: [
|
|
23
|
-
{
|
|
24
|
-
file: "/tmp/tests/auth.spec.js",
|
|
25
|
-
specs: [
|
|
26
|
-
{
|
|
27
|
-
title: "auth works",
|
|
28
|
-
tests: [
|
|
29
|
-
{
|
|
30
|
-
outcome: "unexpected",
|
|
31
|
-
results: [
|
|
32
|
-
{
|
|
33
|
-
status: "failed",
|
|
34
|
-
duration: 15,
|
|
35
|
-
error: {
|
|
36
|
-
message: "boom\nstack",
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
},
|
|
45
|
-
],
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const parsed = parsePlaywrightJsonResults(stdout, "/tmp");
|
|
49
|
-
expect(parsed.errors).toEqual(["reporter failure"]);
|
|
50
|
-
expect(parsed.fileResults.get("tests/auth.spec.js")).toEqual({
|
|
51
|
-
failed: true,
|
|
52
|
-
status: "failed",
|
|
53
|
-
error: "boom",
|
|
54
|
-
durationMs: 15,
|
|
55
|
-
failureDetails: [
|
|
56
|
-
{
|
|
57
|
-
kind: "playwright-spec",
|
|
58
|
-
key: "auth works",
|
|
59
|
-
title: "auth works",
|
|
60
|
-
count: 1,
|
|
61
|
-
message: "boom",
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("marks files as skipped when every Playwright test is skipped", () => {
|
|
68
|
-
const stdout = JSON.stringify({
|
|
69
|
-
suites: [
|
|
70
|
-
{
|
|
71
|
-
file: "/tmp/tests/billing.spec.js",
|
|
72
|
-
specs: [
|
|
73
|
-
{
|
|
74
|
-
title: "billing is stubbed",
|
|
75
|
-
tests: [
|
|
76
|
-
{
|
|
77
|
-
outcome: "skipped",
|
|
78
|
-
results: [
|
|
79
|
-
{
|
|
80
|
-
status: "skipped",
|
|
81
|
-
duration: 7,
|
|
82
|
-
},
|
|
83
|
-
],
|
|
84
|
-
},
|
|
85
|
-
],
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
},
|
|
89
|
-
],
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const parsed = parsePlaywrightJsonResults(stdout, "/tmp");
|
|
93
|
-
expect(parsed.fileResults.get("tests/billing.spec.js")).toEqual({
|
|
94
|
-
failed: false,
|
|
95
|
-
status: "skipped",
|
|
96
|
-
error: null,
|
|
97
|
-
durationMs: 7,
|
|
98
|
-
failureDetails: [],
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("keeps file status passed when one spec passes and another is skipped", () => {
|
|
103
|
-
const stdout = JSON.stringify({
|
|
104
|
-
suites: [
|
|
105
|
-
{
|
|
106
|
-
file: "/tmp/tests/mixed.spec.js",
|
|
107
|
-
specs: [
|
|
108
|
-
{
|
|
109
|
-
title: "passes",
|
|
110
|
-
tests: [
|
|
111
|
-
{
|
|
112
|
-
outcome: "expected",
|
|
113
|
-
results: [
|
|
114
|
-
{
|
|
115
|
-
status: "passed",
|
|
116
|
-
duration: 5,
|
|
117
|
-
},
|
|
118
|
-
],
|
|
119
|
-
},
|
|
120
|
-
],
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
title: "skips",
|
|
124
|
-
tests: [
|
|
125
|
-
{
|
|
126
|
-
outcome: "skipped",
|
|
127
|
-
results: [
|
|
128
|
-
{
|
|
129
|
-
status: "skipped",
|
|
130
|
-
duration: 3,
|
|
131
|
-
},
|
|
132
|
-
],
|
|
133
|
-
},
|
|
134
|
-
],
|
|
135
|
-
},
|
|
136
|
-
],
|
|
137
|
-
},
|
|
138
|
-
],
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
const parsed = parsePlaywrightJsonResults(stdout, "/tmp");
|
|
142
|
-
expect(parsed.fileResults.get("tests/mixed.spec.js")).toEqual({
|
|
143
|
-
failed: false,
|
|
144
|
-
status: "passed",
|
|
145
|
-
error: null,
|
|
146
|
-
durationMs: 8,
|
|
147
|
-
failureDetails: [],
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it("chooses the final result and extracts failures", () => {
|
|
152
|
-
expect(
|
|
153
|
-
choosePlaywrightFinalResult([
|
|
154
|
-
{ status: "failed" },
|
|
155
|
-
{ status: "passed" },
|
|
156
|
-
])
|
|
157
|
-
).toEqual({ status: "passed" });
|
|
158
|
-
|
|
159
|
-
expect(
|
|
160
|
-
extractPlaywrightFailure(
|
|
161
|
-
{ error: { message: "first line\nsecond line" } },
|
|
162
|
-
{ title: "spec title" },
|
|
163
|
-
{ errors: [] }
|
|
164
|
-
)
|
|
165
|
-
).toBe("first line");
|
|
166
|
-
});
|
|
167
|
-
});
|