@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
@@ -0,0 +1,481 @@
1
+ import { fileDisplayName } from "../../discovery/index.mjs";
2
+ import { suiteSelectionType } from "../../runner/suite-selection.mjs";
3
+ import { buildRunSummaryData } from "../../runner/formatting.mjs";
4
+
5
+ export function buildFailureKey(serviceName, filePath) {
6
+ return `${serviceName}::${filePath}`;
7
+ }
8
+
9
+ export function parseFailureKey(failureKey) {
10
+ const separator = String(failureKey || "").indexOf("::");
11
+ if (separator <= 0) return { serviceName: null, filePath: null };
12
+ return {
13
+ serviceName: failureKey.slice(0, separator),
14
+ filePath: failureKey.slice(separator + 2),
15
+ };
16
+ }
17
+
18
+ export function createRunSessionState() {
19
+ const services = new Map();
20
+ let totalCount = 0;
21
+ let completedCount = 0;
22
+ let phase = null;
23
+ let finished = false;
24
+ let summaryData = null;
25
+ let regressionCatalog = null;
26
+ let mode = "running";
27
+ let notice = null;
28
+ let selectedFailureKey = null;
29
+ let agentSession = null;
30
+ const listeners = new Set();
31
+
32
+ function notify() {
33
+ for (const callback of listeners) callback();
34
+ }
35
+
36
+ function getOrCreateService(serviceName) {
37
+ if (!services.has(serviceName)) {
38
+ services.set(serviceName, { name: serviceName, types: new Map(), skipped: false, skipReason: null });
39
+ }
40
+ return services.get(serviceName);
41
+ }
42
+
43
+ function getOrCreateType(service, displayType) {
44
+ if (!service.types.has(displayType)) {
45
+ service.types.set(displayType, { type: displayType, suites: new Map() });
46
+ }
47
+ return service.types.get(displayType);
48
+ }
49
+
50
+ function getOrCreateSuite(typeNode, suiteKey, suiteMeta) {
51
+ if (!typeNode.suites.has(suiteKey)) {
52
+ typeNode.suites.set(suiteKey, {
53
+ name: suiteMeta.name,
54
+ groupLabel: suiteMeta.groupLabel,
55
+ framework: suiteMeta.framework,
56
+ files: new Map(),
57
+ });
58
+ }
59
+ return typeNode.suites.get(suiteKey);
60
+ }
61
+
62
+ function findFile(serviceName, suiteKey, filePath) {
63
+ const service = services.get(serviceName);
64
+ if (!service) return null;
65
+ for (const typeNode of service.types.values()) {
66
+ const suite = typeNode.suites.get(suiteKey);
67
+ if (!suite) continue;
68
+ const file = suite.files.get(filePath);
69
+ if (file) return file;
70
+ }
71
+ return null;
72
+ }
73
+
74
+ function findFileByServiceAndPath(serviceName, filePath) {
75
+ const service = services.get(serviceName);
76
+ if (!service) return null;
77
+ for (const typeNode of service.types.values()) {
78
+ for (const suite of typeNode.suites.values()) {
79
+ const file = suite.files.get(filePath);
80
+ if (file) return file;
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+
86
+ function collectFailedFiles() {
87
+ const failures = [];
88
+ for (const service of services.values()) {
89
+ for (const typeNode of service.types.values()) {
90
+ for (const [suiteKey, suite] of typeNode.suites) {
91
+ for (const file of suite.files.values()) {
92
+ if (file.status !== "failed") continue;
93
+ failures.push({
94
+ key: buildFailureKey(service.name, file.path),
95
+ serviceName: service.name,
96
+ suiteKey,
97
+ suiteName: suite.name,
98
+ displayType: typeNode.type,
99
+ framework: suite.framework,
100
+ filePath: file.path,
101
+ displayName: file.displayName,
102
+ error: file.error,
103
+ failureDetails: file.failureDetails,
104
+ });
105
+ }
106
+ }
107
+ }
108
+ }
109
+ return failures.sort(
110
+ (left, right) =>
111
+ left.serviceName.localeCompare(right.serviceName) || left.filePath.localeCompare(right.filePath)
112
+ );
113
+ }
114
+
115
+ function ensureSelectedFailure() {
116
+ const failures = collectFailedFiles();
117
+ if (failures.length === 0) {
118
+ selectedFailureKey = null;
119
+ return;
120
+ }
121
+ if (selectedFailureKey && failures.some((failure) => failure.key === selectedFailureKey)) return;
122
+ selectedFailureKey = failures[0].key;
123
+ }
124
+
125
+ function appendTranscriptEntry(kind, text) {
126
+ if (!agentSession || !text) return;
127
+ const normalizedText = String(text);
128
+ const entries = agentSession.entries || [];
129
+ const lastEntry = entries.at(-1);
130
+ if (kind === "assistant" && lastEntry?.kind === "assistant") {
131
+ lastEntry.text += normalizedText;
132
+ agentSession.updatedAt = Date.now();
133
+ return;
134
+ }
135
+ entries.push({ kind, text: normalizedText });
136
+ agentSession.entries = entries;
137
+ agentSession.updatedAt = Date.now();
138
+ }
139
+
140
+ function finishAgentSession(status, extra = {}) {
141
+ if (!agentSession) return;
142
+ agentSession = {
143
+ ...agentSession,
144
+ ...extra,
145
+ status,
146
+ endedAt: Date.now(),
147
+ };
148
+ }
149
+
150
+ return {
151
+ initFromPlans(servicePlans) {
152
+ for (const plan of servicePlans) {
153
+ const serviceName = plan.config.name;
154
+ const service = getOrCreateService(serviceName);
155
+
156
+ if (plan.skipped) {
157
+ service.skipped = true;
158
+ continue;
159
+ }
160
+
161
+ for (const suite of plan.suites) {
162
+ const displayType = suite.displayType || suiteSelectionType(suite.type, suite.framework);
163
+ const typeNode = getOrCreateType(service, displayType);
164
+ const suiteKey = `${displayType}:${suite.name}`;
165
+ const suiteNode = getOrCreateSuite(typeNode, suiteKey, {
166
+ name: suite.name,
167
+ groupLabel: fileDisplayName(suite.name),
168
+ framework: suite.framework,
169
+ });
170
+
171
+ for (const filePath of suite.files) {
172
+ if (suiteNode.files.has(filePath)) continue;
173
+ suiteNode.files.set(filePath, {
174
+ path: filePath,
175
+ displayName: fileDisplayName(filePath),
176
+ status: "pending",
177
+ durationMs: null,
178
+ error: null,
179
+ failureDetails: null,
180
+ skipReason: null,
181
+ });
182
+ }
183
+ }
184
+ }
185
+ ensureSelectedFailure();
186
+ notify();
187
+ },
188
+
189
+ setRegressionCatalog(document) {
190
+ regressionCatalog = document;
191
+ notify();
192
+ },
193
+
194
+ markFileRunning(serviceName, suiteKey, filePath) {
195
+ const file = findFile(serviceName, suiteKey, filePath) || findFileByServiceAndPath(serviceName, filePath);
196
+ if (!file) return;
197
+ file.status = "running";
198
+ notify();
199
+ },
200
+
201
+ markFileFinished(task, outcome) {
202
+ const suiteKey = `${task.displayType || suiteSelectionType(task.type, task.framework)}:${task.suiteName}`;
203
+ const file = findFile(task.serviceName, suiteKey, task.file) || findFileByServiceAndPath(task.serviceName, task.file);
204
+ if (!file) return;
205
+ if (outcome.status === "skipped") {
206
+ file.status = "skipped";
207
+ file.skipReason = outcome.reason || null;
208
+ } else if (outcome.status === "not_run") {
209
+ file.status = "not_run";
210
+ file.skipReason = outcome.reason || null;
211
+ } else if (outcome.failed) {
212
+ file.status = "failed";
213
+ file.error = outcome.error || null;
214
+ file.failureDetails = outcome.failureDetails || null;
215
+ } else {
216
+ file.status = "passed";
217
+ }
218
+ file.durationMs = outcome.durationMs || null;
219
+ completedCount += 1;
220
+ ensureSelectedFailure();
221
+ notify();
222
+ },
223
+
224
+ markServiceSkipped(serviceName, reason) {
225
+ const service = getOrCreateService(serviceName);
226
+ service.skipped = true;
227
+ service.skipReason = reason || null;
228
+ notify();
229
+ },
230
+
231
+ markPlannedSkip(entry) {
232
+ const file = findFileByServiceAndPath(entry.serviceName, entry.file);
233
+ if (!file) return;
234
+ file.status = "skipped";
235
+ file.skipReason = entry.reason || null;
236
+ completedCount += 1;
237
+ notify();
238
+ },
239
+
240
+ markRuntimeError(task, message) {
241
+ const file = findFileByServiceAndPath(task.serviceName, task.file);
242
+ if (!file) return;
243
+ file.status = "failed";
244
+ file.error = message || "runtime error";
245
+ completedCount += 1;
246
+ ensureSelectedFailure();
247
+ notify();
248
+ },
249
+
250
+ setTotalFileCount(count) {
251
+ totalCount = count;
252
+ notify();
253
+ },
254
+
255
+ setPhase(label) {
256
+ phase = label;
257
+ notify();
258
+ },
259
+
260
+ finish(results, durationMs, regressionReport) {
261
+ finished = true;
262
+ mode = "complete";
263
+ const summary = buildRunSummaryData(results, durationMs, regressionReport);
264
+ const rows = [
265
+ ["Result", summary.result],
266
+ ["Passed", String(summary.passed)],
267
+ ["Failed", String(summary.failed)],
268
+ ["Skipped", String(summary.skipped)],
269
+ ["Not run", String(summary.notRun)],
270
+ ["Files", String(summary.files)],
271
+ ["Duration", summary.duration],
272
+ ];
273
+ if (summary.serviceErrors > 0) rows.push(["Runtime errors", String(summary.serviceErrors)]);
274
+ if (summary.newRegressions > 0) rows.push(["New regressions", String(summary.newRegressions)]);
275
+ if (summary.knownRegressions > 0) rows.push(["Known regressions", String(summary.knownRegressions)]);
276
+ if (summary.fixedKnownRegressions > 0) rows.push(["Fixed known", String(summary.fixedKnownRegressions)]);
277
+ if (summary.catalogStale > 0) rows.push(["Catalog stale", String(summary.catalogStale)]);
278
+ summaryData = { rows, result: summary.result };
279
+ ensureSelectedFailure();
280
+ notify();
281
+ },
282
+
283
+ setNotice(message) {
284
+ notice = message ? String(message) : null;
285
+ notify();
286
+ },
287
+
288
+ clearNotice() {
289
+ if (!notice) return;
290
+ notice = null;
291
+ notify();
292
+ },
293
+
294
+ selectNextFailure() {
295
+ const failures = collectFailedFiles();
296
+ if (failures.length === 0) return;
297
+ const currentIndex = failures.findIndex((failure) => failure.key === selectedFailureKey);
298
+ const nextIndex = currentIndex < 0 ? 0 : (currentIndex + 1) % failures.length;
299
+ selectedFailureKey = failures[nextIndex].key;
300
+ notify();
301
+ },
302
+
303
+ selectPreviousFailure() {
304
+ const failures = collectFailedFiles();
305
+ if (failures.length === 0) return;
306
+ const currentIndex = failures.findIndex((failure) => failure.key === selectedFailureKey);
307
+ const nextIndex = currentIndex < 0 ? failures.length - 1 : (currentIndex - 1 + failures.length) % failures.length;
308
+ selectedFailureKey = failures[nextIndex].key;
309
+ notify();
310
+ },
311
+
312
+ selectFailure(failureKey) {
313
+ selectedFailureKey = failureKey || null;
314
+ ensureSelectedFailure();
315
+ notify();
316
+ },
317
+
318
+ beginInvestigation({ provider, userMessage } = {}) {
319
+ mode = "investigating";
320
+ notice = null;
321
+ agentSession = {
322
+ provider: provider || "auto",
323
+ userMessage: userMessage || "",
324
+ status: "starting",
325
+ startedAt: Date.now(),
326
+ updatedAt: Date.now(),
327
+ entries: [],
328
+ };
329
+ notify();
330
+ },
331
+
332
+ appendAgentEvent(event) {
333
+ if (!agentSession || !event) return;
334
+ if (event.type === "start") {
335
+ agentSession.status = "running";
336
+ } else if (event.type === "delta") {
337
+ appendTranscriptEntry("assistant", event.text || "");
338
+ } else if (event.type === "final") {
339
+ if (event.text && !(agentSession.entries || []).some((entry) => entry.kind === "assistant")) {
340
+ appendTranscriptEntry("assistant", event.text);
341
+ }
342
+ agentSession.finalText = event.text || agentSession.finalText || "";
343
+ } else if (event.type === "tool") {
344
+ appendTranscriptEntry("tool", event.detail ? `${event.name}: ${event.detail}` : event.name);
345
+ } else if (event.type === "status") {
346
+ appendTranscriptEntry("status", event.message || "");
347
+ } else if (event.type === "error") {
348
+ appendTranscriptEntry("error", event.message || "Agent error");
349
+ } else if (event.type === "exit") {
350
+ agentSession.exitCode = event.code;
351
+ }
352
+ notify();
353
+ },
354
+
355
+ completeAgentSession(result = {}) {
356
+ finishAgentSession("complete", {
357
+ finalText: result.finalText || agentSession?.finalText || "",
358
+ exitCode: result.exitCode ?? agentSession?.exitCode ?? 0,
359
+ });
360
+ notify();
361
+ },
362
+
363
+ failAgentSession(error) {
364
+ finishAgentSession("error", {
365
+ error: error instanceof Error ? error.message : String(error || "Agent error"),
366
+ });
367
+ notify();
368
+ },
369
+
370
+ cancelAgentSession(message = "Cancelled investigation.") {
371
+ finishAgentSession("cancelled");
372
+ mode = "complete";
373
+ notice = message;
374
+ notify();
375
+ },
376
+
377
+ returnToSummary() {
378
+ mode = "complete";
379
+ notify();
380
+ },
381
+
382
+ subscribe(callback) {
383
+ listeners.add(callback);
384
+ return () => listeners.delete(callback);
385
+ },
386
+
387
+ getSnapshot() {
388
+ const serviceSnapshots = [];
389
+ for (const service of services.values()) {
390
+ if (service.skipped) {
391
+ serviceSnapshots.push({
392
+ name: service.name,
393
+ skipped: true,
394
+ skipReason: service.skipReason,
395
+ types: [],
396
+ });
397
+ continue;
398
+ }
399
+
400
+ const typeSnapshots = [];
401
+ for (const typeNode of service.types.values()) {
402
+ const suiteSnapshots = [];
403
+ let typeAllCollapsed = true;
404
+
405
+ for (const [suiteKey, suite] of typeNode.suites) {
406
+ const files = [...suite.files.values()];
407
+ const allPassed = files.length > 0 && files.every((file) => file.status === "passed");
408
+ const allSkipped = files.length > 0 && files.every((file) => file.status === "skipped");
409
+ const anyFailed = files.some((file) => file.status === "failed");
410
+ const anyRunning = files.some((file) => file.status === "running");
411
+ const anyPending = files.some((file) => file.status === "pending");
412
+
413
+ let collapsed = false;
414
+ let collapseStatus = null;
415
+ let visibleFiles = files;
416
+
417
+ if (allPassed) {
418
+ collapsed = true;
419
+ collapseStatus = "all_passed";
420
+ visibleFiles = [];
421
+ } else if (allSkipped) {
422
+ collapsed = true;
423
+ collapseStatus = "all_skipped";
424
+ visibleFiles = [];
425
+ } else if (anyFailed && !anyRunning && !anyPending) {
426
+ visibleFiles = files.filter((file) => file.status === "failed" || file.status === "not_run");
427
+ }
428
+
429
+ if (!collapsed) {
430
+ typeAllCollapsed = false;
431
+ }
432
+
433
+ suiteSnapshots.push({
434
+ key: suiteKey,
435
+ name: suite.name,
436
+ groupLabel: suite.groupLabel,
437
+ framework: suite.framework,
438
+ collapsed,
439
+ collapseStatus,
440
+ fileCount: files.length,
441
+ passedCount: files.filter((file) => file.status === "passed").length,
442
+ totalDurationMs: files.reduce((sum, file) => sum + (file.durationMs || 0), 0),
443
+ visibleFiles,
444
+ });
445
+ }
446
+
447
+ typeSnapshots.push({
448
+ type: typeNode.type,
449
+ collapsed: typeAllCollapsed,
450
+ suites: suiteSnapshots,
451
+ });
452
+ }
453
+
454
+ serviceSnapshots.push({
455
+ name: service.name,
456
+ skipped: false,
457
+ skipReason: null,
458
+ types: typeSnapshots,
459
+ });
460
+ }
461
+
462
+ const failures = collectFailedFiles();
463
+ const selectedFailure = failures.find((failure) => failure.key === selectedFailureKey) || null;
464
+ return {
465
+ services: serviceSnapshots,
466
+ completedCount,
467
+ totalCount,
468
+ phase,
469
+ finished,
470
+ summaryData,
471
+ regressionCatalog,
472
+ mode,
473
+ notice,
474
+ failures,
475
+ selectedFailureKey,
476
+ selectedFailure,
477
+ agentSession,
478
+ };
479
+ },
480
+ };
481
+ }
@@ -0,0 +1 @@
1
+ export { createRunSessionState as createRunTreeState } from "./run-session-state.mjs";
@@ -250,16 +250,21 @@ function resolveActorSession({ actorDefinition, actorIndex, contract, env }) {
250
250
  };
251
251
 
252
252
  if (contract.signup.enabled) {
253
- runProfileRequest({
254
- requestConfig: {
255
- body: () => buildSignupBody(actorDefinition),
256
- expect: contract.signup.expect,
257
- method: "POST",
258
- path: contract.signup.path,
259
- },
260
- context: { ...context, phase: "signup" },
261
- label: `auth.fixture signup for actor "${actorDefinition.actorName}"`,
262
- });
253
+ try {
254
+ runProfileRequest({
255
+ requestConfig: {
256
+ body: () => buildSignupBody(actorDefinition),
257
+ expect: contract.signup.expect,
258
+ method: "POST",
259
+ path: contract.signup.path,
260
+ },
261
+ context: { ...context, phase: "signup" },
262
+ label: `auth.fixture signup for actor "${actorDefinition.actorName}"`,
263
+ });
264
+ } catch {
265
+ // Provisioning is best-effort. Some apps report duplicate-account races as 500s
266
+ // instead of a clean 409, and a successful login is the authoritative signal.
267
+ }
263
268
  }
264
269
 
265
270
  const response = runProfileRequest({
@@ -494,7 +494,7 @@ function normalizePath(filePath) {
494
494
  return String(filePath).split(path.sep).join("/").replace(/^\.\/+/, "");
495
495
  }
496
496
 
497
- function fileDisplayName(filePath) {
497
+ export function fileDisplayName(filePath) {
498
498
  const base = path.posix
499
499
  .basename(filePath)
500
500
  .replace(/(\.int|\.e2e|\.scenario|\.dal|\.load|\.pw)\.testkit\.ts$/, "");
package/lib/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import type {
2
2
  ActorRequestClient,
3
3
  HttpClient,
4
4
  HttpClientConfig,
5
+ RawRequestClient,
5
6
  RuntimeDb,
6
7
  RuntimeDalContext,
7
8
  RuntimeEnv,
@@ -33,11 +34,14 @@ export interface AuthAdapter<TSetup = unknown> {
33
34
 
34
35
  export interface SuiteActor<TSession = Record<string, unknown>> {
35
36
  email: string | null;
37
+ headers: RuntimeHeaders;
36
38
  index: number;
37
39
  key: string;
38
40
  name: string | null;
39
41
  organizationKey: string | null;
40
42
  organizationName: string | null;
43
+ rawHeaders: RuntimeHeaders;
44
+ rawReq: RawRequestClient;
41
45
  req: ActorRequestClient;
42
46
  session: TSession | null;
43
47
  }
@@ -55,7 +59,7 @@ export interface HttpSuiteContext<TSession = Record<string, unknown>> {
55
59
  actors: SuiteActors<TSession>;
56
60
  env: RuntimeEnv;
57
61
  req: HttpClient<TSession>;
58
- rawReq: HttpClient["raw"];
62
+ rawReq: RawRequestClient;
59
63
  }
60
64
 
61
65
  export interface ScenarioStepResult {
@@ -85,6 +85,7 @@ export async function runAll(configs, typeValues, suiteSelectors, opts, allConfi
85
85
  execution,
86
86
  reporter
87
87
  );
88
+ reporter?.setServicePlans?.(servicePlans);
88
89
  const trackers = buildServiceTrackers(servicePlans, startedAt);
89
90
  let writeLiveSnapshot = () => {};
90
91
  const setupRegistry = createSetupOperationRegistry({ logRegistry, onChange: () => writeLiveSnapshot() });