@elench/testkit 0.1.82 → 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.
Files changed (100) hide show
  1. package/README.md +37 -7
  2. package/lib/cli/agents/index.mjs +64 -0
  3. package/lib/cli/agents/investigate.mjs +75 -0
  4. package/lib/cli/agents/investigation-context.mjs +102 -0
  5. package/lib/cli/agents/prompt-builder.mjs +25 -0
  6. package/lib/cli/agents/providers/claude.mjs +74 -0
  7. package/lib/cli/agents/providers/codex.mjs +83 -0
  8. package/lib/cli/agents/providers/shared.mjs +134 -0
  9. package/lib/cli/command-helpers.mjs +53 -25
  10. package/lib/cli/commands/investigate.mjs +87 -0
  11. package/lib/cli/entrypoint.mjs +3 -0
  12. package/lib/cli/presentation/colors.mjs +12 -0
  13. package/lib/cli/presentation/events-reporter.mjs +135 -0
  14. package/lib/cli/presentation/summary-box.mjs +11 -11
  15. package/lib/cli/presentation/tree-reporter.mjs +159 -0
  16. package/lib/cli/tui/run-app.mjs +1 -0
  17. package/lib/cli/tui/run-session-app.mjs +370 -0
  18. package/lib/cli/tui/run-session-state.mjs +481 -0
  19. package/lib/cli/tui/run-tree-state.mjs +1 -0
  20. package/lib/config-api/auth-fixtures.mjs +15 -10
  21. package/lib/discovery/index.mjs +1 -1
  22. package/lib/index.d.ts +5 -1
  23. package/lib/runner/orchestrator.mjs +1 -0
  24. package/lib/runtime/index.d.ts +138 -5
  25. package/lib/runtime/index.mjs +68 -2
  26. package/lib/runtime-src/k6/http-assertions.js +31 -1
  27. package/lib/runtime-src/k6/http-checks.js +120 -0
  28. package/lib/runtime-src/k6/http-suite-runtime.js +5 -1
  29. package/lib/runtime-src/k6/http.js +213 -23
  30. package/lib/runtime-src/shared/error-body.mjs +42 -0
  31. package/lib/runtime-src/shared/http-parsing.mjs +68 -0
  32. package/node_modules/@elench/next-analysis/package.json +1 -1
  33. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  34. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  35. package/node_modules/@elench/ts-analysis/package.json +1 -1
  36. package/package.json +7 -6
  37. package/lib/app/configs.test.mjs +0 -34
  38. package/lib/app/typecheck.test.mjs +0 -24
  39. package/lib/bundler/index.test.mjs +0 -164
  40. package/lib/cli/args.test.mjs +0 -110
  41. package/lib/cli/presentation/code-frames.test.mjs +0 -71
  42. package/lib/cli/presentation/run-reporter.test.mjs +0 -192
  43. package/lib/cli/presentation/summary-box.test.mjs +0 -43
  44. package/lib/cli/presentation/terminal-layout.test.mjs +0 -23
  45. package/lib/config/database.test.mjs +0 -29
  46. package/lib/config/discovery.test.mjs +0 -276
  47. package/lib/config/env.test.mjs +0 -40
  48. package/lib/config/index.test.mjs +0 -44
  49. package/lib/config/paths.test.mjs +0 -27
  50. package/lib/config/runtime.test.mjs +0 -82
  51. package/lib/config/skip-config.test.mjs +0 -63
  52. package/lib/config-api/index.test.mjs +0 -344
  53. package/lib/config-api/next-runtime-tsconfig.test.mjs +0 -58
  54. package/lib/coverage/backend-discovery.test.mjs +0 -61
  55. package/lib/coverage/evidence.test.mjs +0 -87
  56. package/lib/coverage/index.test.mjs +0 -715
  57. package/lib/coverage/routing.test.mjs +0 -36
  58. package/lib/coverage/shared.test.mjs +0 -72
  59. package/lib/database/fingerprint.test.mjs +0 -99
  60. package/lib/database/index.test.mjs +0 -95
  61. package/lib/database/naming.test.mjs +0 -39
  62. package/lib/database/state.test.mjs +0 -66
  63. package/lib/database/template-steps.test.mjs +0 -43
  64. package/lib/discovery/file-metadata.test.mjs +0 -51
  65. package/lib/discovery/index.test.mjs +0 -182
  66. package/lib/discovery/path-policy.test.mjs +0 -65
  67. package/lib/drizzle/index.test.mjs +0 -33
  68. package/lib/env/index.test.mjs +0 -82
  69. package/lib/history/index.test.mjs +0 -115
  70. package/lib/package.test.mjs +0 -59
  71. package/lib/playwright/index.test.mjs +0 -43
  72. package/lib/regressions/github.test.mjs +0 -324
  73. package/lib/regressions/index.test.mjs +0 -187
  74. package/lib/reporters/playwright.test.mjs +0 -167
  75. package/lib/runner/default-runtime-errors.test.mjs +0 -49
  76. package/lib/runner/execution-config.test.mjs +0 -67
  77. package/lib/runner/failure-details.test.mjs +0 -114
  78. package/lib/runner/formatting.test.mjs +0 -205
  79. package/lib/runner/metadata.test.mjs +0 -52
  80. package/lib/runner/planning.test.mjs +0 -371
  81. package/lib/runner/playwright-config.test.mjs +0 -78
  82. package/lib/runner/processes.test.mjs +0 -21
  83. package/lib/runner/regressions.test.mjs +0 -168
  84. package/lib/runner/reporting.test.mjs +0 -310
  85. package/lib/runner/results.test.mjs +0 -376
  86. package/lib/runner/runtime-manager.test.mjs +0 -252
  87. package/lib/runner/runtime-preparation.test.mjs +0 -141
  88. package/lib/runner/selection.test.mjs +0 -24
  89. package/lib/runner/setup-operations.test.mjs +0 -94
  90. package/lib/runner/state.test.mjs +0 -62
  91. package/lib/runner/suite-selection.test.mjs +0 -49
  92. package/lib/runner/template.test.mjs +0 -272
  93. package/lib/shared/build-config.test.mjs +0 -132
  94. package/lib/shared/configured-steps.test.mjs +0 -102
  95. package/lib/shared/execution-schema.test.mjs +0 -26
  96. package/lib/shared/file-timeout.test.mjs +0 -64
  97. package/lib/shared/test-context.test.mjs +0 -43
  98. package/lib/timing/index.test.mjs +0 -64
  99. package/lib/toolchains/index.test.mjs +0 -168
  100. package/lib/vitest/index.test.mjs +0 -20
@@ -1,49 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- determineDefaultRuntimeFailure,
4
- extractDefaultRuntimeFatalError,
5
- extractDefaultRuntimeThresholdFailures,
6
- sanitizeDefaultRuntimeExitError,
7
- } from "./default-runtime-errors.mjs";
8
-
9
- const firstLine = (value) => value.split(/\r?\n/)[0];
10
-
11
- describe("default runtime errors", () => {
12
- it("extracts fatal stacktrace errors", () => {
13
- expect(
14
- extractDefaultRuntimeFatalError("source=stacktrace\nError: boom\nmore", firstLine)
15
- ).toBe("boom");
16
- });
17
-
18
- it("extracts failed thresholds", () => {
19
- expect(
20
- extractDefaultRuntimeThresholdFailures({
21
- metrics: {
22
- checks: {
23
- thresholds: {
24
- "rate==1": true,
25
- "rate>0.9": false,
26
- },
27
- },
28
- other: {},
29
- },
30
- })
31
- ).toEqual(["checks(rate==1)"]);
32
- });
33
-
34
- it("sanitizes exit errors", () => {
35
- expect(sanitizeDefaultRuntimeExitError(99, "bad\nstack", firstLine)).toBe(
36
- "Default runtime failed with exit code 99: bad"
37
- );
38
- });
39
-
40
- it("chooses the highest-priority runtime failure", () => {
41
- expect(
42
- determineDefaultRuntimeFailure(
43
- { exitCode: 0, stderr: "source=stacktrace\nError: boom", stdout: "" },
44
- null,
45
- firstLine
46
- )
47
- ).toBe("Default runtime uncaught error: boom");
48
- });
49
- });
@@ -1,67 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- buildRuntimeIds,
4
- DATABASE_BINDINGS,
5
- DEFAULT_FILE_TIMEOUT_SECONDS,
6
- normalizeDatabaseBinding,
7
- normalizeExecutionConfig,
8
- normalizeRuntimeMaxConcurrentTasks,
9
- normalizeRuntimeInstances,
10
- parseFileTimeoutOption,
11
- parseRuntimeMaxConcurrentTasksOption,
12
- parseRuntimeInstancesOption,
13
- parseWorkersOption,
14
- resolveExecutionConfig,
15
- } from "./execution-config.mjs";
16
-
17
- describe("execution-config", () => {
18
- it("parses worker and runtime-instance options", () => {
19
- expect(parseWorkersOption("8")).toBe(8);
20
- expect(parseRuntimeInstancesOption("2")).toBe(2);
21
- expect(parseRuntimeMaxConcurrentTasksOption("4")).toBe(4);
22
- expect(parseFileTimeoutOption("45")).toBe(45);
23
- expect(() => parseWorkersOption("0")).toThrow('Invalid --workers value "0"');
24
- expect(() => parseRuntimeInstancesOption("0")).toThrow('Invalid runtime.instances value "0"');
25
- expect(() => parseRuntimeMaxConcurrentTasksOption("0")).toThrow(
26
- 'Invalid runtime.maxConcurrentTasks value "0"'
27
- );
28
- expect(() => parseFileTimeoutOption("0")).toThrow(
29
- 'Invalid --file-timeout-seconds value "0"'
30
- );
31
- });
32
-
33
- it("normalizes execution defaults", () => {
34
- expect(normalizeExecutionConfig({ workers: 8 })).toEqual({
35
- workers: 8,
36
- fileTimeoutSeconds: DEFAULT_FILE_TIMEOUT_SECONDS,
37
- });
38
- });
39
-
40
- it("applies CLI values over repo execution defaults", () => {
41
- expect(
42
- resolveExecutionConfig({
43
- repo: { workers: 3, fileTimeoutSeconds: 30 },
44
- cli: { workers: 6, fileTimeoutSeconds: 90 },
45
- })
46
- ).toEqual({
47
- workers: 6,
48
- fileTimeoutSeconds: 90,
49
- });
50
- });
51
-
52
- it("builds runtime ids from an instance count", () => {
53
- expect(buildRuntimeIds(3)).toEqual(["runtime-1", "runtime-2", "runtime-3"]);
54
- });
55
-
56
- it("normalizes runtime instances and database bindings", () => {
57
- expect(normalizeRuntimeInstances(2)).toBe(2);
58
- expect(normalizeRuntimeMaxConcurrentTasks(undefined)).toBe(Number.POSITIVE_INFINITY);
59
- expect(normalizeRuntimeMaxConcurrentTasks(3)).toBe(3);
60
- for (const binding of DATABASE_BINDINGS) {
61
- expect(normalizeDatabaseBinding(binding)).toBe(binding);
62
- }
63
- expect(() => normalizeDatabaseBinding("legacy")).toThrow(
64
- 'Invalid database.binding value "legacy"'
65
- );
66
- });
67
- });
@@ -1,114 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- collectFailureDetailsFromRuntimeArtifacts,
4
- mergeFailureDetails,
5
- } from "./failure-details.mjs";
6
-
7
- describe("runner failure details", () => {
8
- it("merges duplicate failure details by kind and key", () => {
9
- expect(
10
- mergeFailureDetails([
11
- { kind: "k6-check", key: "group > check", title: "check" },
12
- { kind: "k6-check", key: "group > check", title: "check" },
13
- { kind: "playwright-spec", key: "spec title", title: "spec title" },
14
- ])
15
- ).toEqual([
16
- {
17
- kind: "k6-check",
18
- key: "group > check",
19
- title: "check",
20
- count: 2,
21
- },
22
- {
23
- kind: "playwright-spec",
24
- key: "spec title",
25
- title: "spec title",
26
- count: 1,
27
- },
28
- ]);
29
- });
30
-
31
- it("extracts and normalizes runtime failure-detail artifacts", () => {
32
- expect(
33
- collectFailureDetailsFromRuntimeArtifacts([
34
- {
35
- kind: "testkit.failure-details",
36
- data: {
37
- phase: "exec",
38
- failures: [
39
- {
40
- kind: "k6-check",
41
- key: "status is 200",
42
- title: "status is 200",
43
- },
44
- {
45
- kind: "k6-check",
46
- key: "status is 200",
47
- title: "status is 200",
48
- },
49
- ],
50
- },
51
- },
52
- ])
53
- ).toEqual([
54
- {
55
- kind: "k6-check",
56
- key: "status is 200",
57
- title: "status is 200",
58
- count: 2,
59
- phase: "exec",
60
- },
61
- ]);
62
- });
63
-
64
- it("preserves rich assertion metadata", () => {
65
- expect(
66
- mergeFailureDetails([
67
- {
68
- kind: "http-assertion",
69
- key: "GET /health > status is 200",
70
- title: "status is 200",
71
- expected: 200,
72
- actual: 404,
73
- request: {
74
- method: "GET",
75
- path: "/health",
76
- requestId: "req-1",
77
- },
78
- response: {
79
- status: 404,
80
- bodyPreview: '{"error":"nope"}',
81
- },
82
- location: {
83
- path: "/tmp/example.ts",
84
- line: 12,
85
- column: 4,
86
- },
87
- },
88
- ])
89
- ).toEqual([
90
- {
91
- kind: "http-assertion",
92
- key: "GET /health > status is 200",
93
- title: "status is 200",
94
- count: 1,
95
- expected: 200,
96
- actual: 404,
97
- request: {
98
- method: "GET",
99
- path: "/health",
100
- requestId: "req-1",
101
- },
102
- response: {
103
- status: 404,
104
- bodyPreview: '{"error":"nope"}',
105
- },
106
- location: {
107
- path: "/tmp/example.ts",
108
- line: 12,
109
- column: 4,
110
- },
111
- },
112
- ]);
113
- });
114
- });
@@ -1,205 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- buildFailurePresentation,
4
- buildRunSummaryData,
5
- buildRunSummaryLines,
6
- formatDuration,
7
- formatError,
8
- formatFrameworkLabel,
9
- formatServiceSummary,
10
- formatSuiteFramework,
11
- longestServiceName,
12
- } from "./formatting.mjs";
13
-
14
- describe("runner formatting", () => {
15
- it("formats durations compactly", () => {
16
- expect(formatDuration(900)).toBe("1s");
17
- expect(formatDuration(61_000)).toBe("1m 01s");
18
- });
19
-
20
- it("sanitizes default-runtime error messages", () => {
21
- expect(
22
- formatError(new Error("Command failed with exit code 99: /tmp/vendor/k6 run test.js"))
23
- ).toBe("Default runtime failed with exit code 99: test.js");
24
- });
25
-
26
- it("formats service summaries with not-run files", () => {
27
- expect(
28
- formatServiceSummary({
29
- completedSuiteCount: 2,
30
- failedSuiteCount: 1,
31
- skippedSuiteCount: 0,
32
- suiteCount: 3,
33
- totalFileCount: 5,
34
- passedFileCount: 3,
35
- skippedFileCount: 0,
36
- notRunFileCount: 1,
37
- })
38
- ).toBe("1/3 suites passed, 3/5 files passed, 1 suite not run");
39
- });
40
-
41
- it("formats skipped suites distinctly from passed suites", () => {
42
- expect(
43
- formatServiceSummary({
44
- completedSuiteCount: 2,
45
- failedSuiteCount: 0,
46
- skippedSuiteCount: 1,
47
- suiteCount: 2,
48
- totalFileCount: 3,
49
- passedFileCount: 1,
50
- skippedFileCount: 2,
51
- notRunFileCount: 0,
52
- })
53
- ).toBe("1/2 suites passed, 1/3 files passed, 1 suite skipped");
54
- });
55
-
56
- it("formats framework labels", () => {
57
- expect(formatFrameworkLabel("k6")).toBe("");
58
- expect(formatFrameworkLabel("playwright")).toBe("playwright");
59
- expect(formatSuiteFramework("playwright")).toBe(" [playwright]");
60
- });
61
-
62
- it("computes longest service name", () => {
63
- expect(longestServiceName([{ name: "api" }, { name: "frontend" }])).toBe(8);
64
- });
65
-
66
- it("builds aggregate summary data without failure dumps", () => {
67
- const summary = buildRunSummaryData(
68
- [
69
- {
70
- name: "frontend",
71
- skipped: false,
72
- failed: true,
73
- suiteCount: 2,
74
- completedSuiteCount: 2,
75
- skippedSuiteCount: 0,
76
- failedSuiteCount: 1,
77
- totalFileCount: 3,
78
- passedFileCount: 2,
79
- failedFileCount: 1,
80
- skippedFileCount: 0,
81
- notRunFileCount: 0,
82
- durationMs: 20_000,
83
- suites: [],
84
- errors: ["worker broke"],
85
- },
86
- ],
87
- 20_000,
88
- {
89
- summary: {
90
- newRegressions: 1,
91
- knownRegressions: 2,
92
- fixedKnownRegressions: 3,
93
- catalogStale: 4,
94
- catalogSyncUnavailable: true,
95
- usedStaleCache: false,
96
- },
97
- }
98
- );
99
-
100
- expect(summary).toMatchObject({
101
- result: "FAILED",
102
- passed: 2,
103
- failed: 1,
104
- skipped: 0,
105
- notRun: 0,
106
- files: 3,
107
- duration: "20s",
108
- serviceErrors: 1,
109
- });
110
- expect(summary.newRegressions).toBe(1);
111
- expect(summary.knownRegressions).toBe(2);
112
- expect(summary.fixedKnownRegressions).toBe(3);
113
- expect(summary.catalogStale).toBe(4);
114
- expect(summary.catalogSyncUnavailable).toBe(true);
115
- });
116
-
117
- it("builds compact summary lines without failure and runtime dump sections", () => {
118
- const lines = buildRunSummaryLines(
119
- [
120
- {
121
- name: "api",
122
- skipped: false,
123
- failed: true,
124
- suiteCount: 1,
125
- completedSuiteCount: 1,
126
- skippedSuiteCount: 0,
127
- failedSuiteCount: 1,
128
- totalFileCount: 1,
129
- passedFileCount: 0,
130
- failedFileCount: 1,
131
- skippedFileCount: 0,
132
- notRunFileCount: 0,
133
- durationMs: 500,
134
- suites: [],
135
- errors: ["worker broke"],
136
- },
137
- ],
138
- 500
139
- );
140
-
141
- expect(lines.join("\n")).toContain("Summary: 0 passed, 1 failed, 0 skipped, 0 not run across 1 file");
142
- expect(lines.join("\n")).toContain("Runtime errors: 1");
143
- expect(lines.join("\n")).not.toContain("Catalog issues:");
144
- expect(lines.join("\n")).not.toContain("Failures:");
145
- expect(lines.join("\n")).not.toContain("Runtime Errors:");
146
- });
147
-
148
- it("builds inline failure presentations from structured HTTP assertion details", () => {
149
- const presentation = buildFailurePresentation(
150
- {
151
- service: "api",
152
- type: "int",
153
- path: "__testkit__/health/health.int.testkit.ts",
154
- error: "Default runtime thresholds failed: checks(rate==1.0)",
155
- failureDetails: [
156
- {
157
- kind: "http-assertion",
158
- key: "GET /health > status is 200",
159
- title: "status is 200",
160
- message: "GET /health expected 200, got 404",
161
- request: {
162
- method: "GET",
163
- path: "/health",
164
- requestId: "req-1",
165
- },
166
- response: {
167
- status: 404,
168
- bodyPreview: '{"error":"nope"}',
169
- },
170
- },
171
- ],
172
- },
173
- {
174
- schemaVersion: 1,
175
- issueRepo: "acme/example",
176
- entries: [
177
- {
178
- id: "health-is-bad",
179
- classification: "product_bug",
180
- issue: {
181
- repo: "acme/example",
182
- number: 42,
183
- },
184
- summary: "Health is bad",
185
- cause: "because",
186
- lastReviewedAt: "2026-01-01",
187
- fingerprints: [
188
- {
189
- service: "api",
190
- type: "int",
191
- path: "__testkit__/health/health.int.testkit.ts",
192
- failureKey: "GET /health > status is 200",
193
- },
194
- ],
195
- },
196
- ],
197
- }
198
- );
199
-
200
- expect(presentation.primary).toBe("GET /health expected 200, got 404");
201
- expect(presentation.details).toContain('response: {"error":"nope"}');
202
- expect(presentation.details).toContain("regression: known #42 product_bug");
203
- expect(presentation.details).toContain("logs: requestId=req-1");
204
- });
205
- });
@@ -1,52 +0,0 @@
1
- import fs from "fs";
2
- import os from "os";
3
- import path from "path";
4
- import { execFileSync } from "child_process";
5
- import { afterEach, describe, expect, it } from "vitest";
6
- import {
7
- collectGitMetadata,
8
- readPackageMetadata,
9
- safeHostname,
10
- safeUsername,
11
- } from "./metadata.mjs";
12
-
13
- const tempDirs = [];
14
-
15
- afterEach(() => {
16
- for (const dir of tempDirs.splice(0)) {
17
- fs.rmSync(dir, { recursive: true, force: true });
18
- }
19
- });
20
-
21
- function mkTempDir() {
22
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-runner-metadata-"));
23
- tempDirs.push(dir);
24
- return dir;
25
- }
26
-
27
- describe("runner-metadata", () => {
28
- it("reads package metadata and safe host/user info", () => {
29
- const pkg = readPackageMetadata();
30
- const packageJson = JSON.parse(
31
- fs.readFileSync(path.resolve(process.cwd(), "package.json"), "utf8")
32
- );
33
- expect(pkg.version).toBe(packageJson.version);
34
- expect(typeof safeHostname()).toBe("string");
35
- expect(typeof safeUsername()).toBe("string");
36
- });
37
-
38
- it("collects git metadata from a real repo", () => {
39
- const repoDir = mkTempDir();
40
- execFileSync("git", ["init", "-b", "main"], { cwd: repoDir, stdio: "ignore" });
41
- execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: repoDir, stdio: "ignore" });
42
- execFileSync("git", ["config", "user.name", "Test User"], { cwd: repoDir, stdio: "ignore" });
43
- fs.writeFileSync(path.join(repoDir, "README.md"), "# test\n");
44
- execFileSync("git", ["add", "README.md"], { cwd: repoDir, stdio: "ignore" });
45
- execFileSync("git", ["commit", "-m", "init"], { cwd: repoDir, stdio: "ignore" });
46
-
47
- const metadata = collectGitMetadata(repoDir);
48
- expect(metadata.branch).toBe("main");
49
- expect(metadata.commitSha).toMatch(/^[a-f0-9]{40}$/);
50
- expect(metadata.repoRoot).toBe(repoDir);
51
- });
52
- });