@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.
- package/README.md +37 -7
- package/lib/cli/agents/index.mjs +64 -0
- package/lib/cli/agents/investigate.mjs +75 -0
- package/lib/cli/agents/investigation-context.mjs +102 -0
- package/lib/cli/agents/prompt-builder.mjs +25 -0
- package/lib/cli/agents/providers/claude.mjs +74 -0
- package/lib/cli/agents/providers/codex.mjs +83 -0
- package/lib/cli/agents/providers/shared.mjs +134 -0
- package/lib/cli/command-helpers.mjs +53 -25
- package/lib/cli/commands/investigate.mjs +87 -0
- package/lib/cli/entrypoint.mjs +3 -0
- package/lib/cli/presentation/colors.mjs +12 -0
- package/lib/cli/presentation/events-reporter.mjs +135 -0
- package/lib/cli/presentation/summary-box.mjs +11 -11
- package/lib/cli/presentation/tree-reporter.mjs +159 -0
- package/lib/cli/tui/run-app.mjs +1 -0
- package/lib/cli/tui/run-session-app.mjs +370 -0
- package/lib/cli/tui/run-session-state.mjs +481 -0
- package/lib/cli/tui/run-tree-state.mjs +1 -0
- package/lib/config-api/auth-fixtures.mjs +15 -10
- package/lib/discovery/index.mjs +1 -1
- package/lib/index.d.ts +5 -1
- package/lib/runner/orchestrator.mjs +1 -0
- package/lib/runtime/index.d.ts +138 -5
- package/lib/runtime/index.mjs +68 -2
- package/lib/runtime-src/k6/http-assertions.js +31 -1
- package/lib/runtime-src/k6/http-checks.js +120 -0
- package/lib/runtime-src/k6/http-suite-runtime.js +5 -1
- package/lib/runtime-src/k6/http.js +213 -23
- package/lib/runtime-src/shared/error-body.mjs +42 -0
- package/lib/runtime-src/shared/http-parsing.mjs +68 -0
- 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/args.test.mjs +0 -110
- package/lib/cli/presentation/code-frames.test.mjs +0 -71
- package/lib/cli/presentation/run-reporter.test.mjs +0 -192
- package/lib/cli/presentation/summary-box.test.mjs +0 -43
- package/lib/cli/presentation/terminal-layout.test.mjs +0 -23
- 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 -344
- 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/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
|
@@ -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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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({
|
package/lib/discovery/index.mjs
CHANGED
|
@@ -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:
|
|
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() });
|