@elench/testkit 0.1.111 → 0.1.113
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 +1 -1
- package/lib/bundler/index.mjs +95 -1
- package/lib/cli/args.mjs +1 -1
- package/lib/cli/assistant/app.mjs +70 -20
- package/lib/cli/assistant/command-normalize.mjs +22 -0
- package/lib/cli/assistant/command-observer.mjs +49 -4
- package/lib/cli/assistant/command-results.mjs +10 -1
- package/lib/cli/assistant/context-pack.mjs +45 -15
- package/lib/cli/assistant/domain.d.mts +59 -0
- package/lib/cli/assistant/domain.d.mts.map +1 -0
- package/lib/cli/assistant/domain.mjs +2 -0
- package/lib/cli/assistant/domain.mjs.map +1 -0
- package/lib/cli/assistant/session.mjs +3 -1
- package/lib/cli/assistant/state.mjs +109 -2
- package/lib/cli/assistant/view-model.mjs +69 -9
- package/lib/cli/commands/run.mjs +1 -1
- package/lib/cli/components/blocks/run-tree.mjs +30 -64
- package/lib/cli/entrypoint.mjs +1 -1
- package/lib/cli/renderers/run/inline-detail.mjs +64 -0
- package/lib/cli/state/run/model.mjs +24 -95
- package/lib/cli/state/run/state.mjs +0 -22
- package/lib/config/discovery.mjs +0 -10
- package/lib/discovery/index.mjs +1 -1
- package/lib/domain/test-types.mjs +5 -14
- package/lib/runner/default-runtime-runner.mjs +3 -1
- package/lib/runner/failure-details.mjs +22 -0
- package/lib/runner/maintenance.mjs +1 -1
- package/lib/runner/provenance.mjs +4 -1
- package/lib/runner/results.mjs +31 -0
- package/lib/runner/status-model.mjs +15 -7
- package/lib/runner/suite-selection.mjs +2 -3
- 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 +5 -5
- package/lib/cli/components/primitives/filter-bar.mjs +0 -12
- package/lib/cli/state/tree/fuzzy-match.mjs +0 -106
- package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.d.ts +0 -188
- package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.d.ts.map +0 -1
- package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.js +0 -293
- package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.js.map +0 -1
- package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/package.json +0 -25
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { fileDisplayName } from "../../../discovery/index.mjs";
|
|
2
2
|
import { suiteSelectionType } from "../../../runner/suite-selection.mjs";
|
|
3
3
|
import { formatDuration } from "../../../runner/formatting.mjs";
|
|
4
|
-
import { matchRunTreeEntry } from "../tree/fuzzy-match.mjs";
|
|
5
4
|
|
|
6
5
|
export function buildSummaryRows({
|
|
7
6
|
result,
|
|
@@ -51,9 +50,6 @@ export function createEmptyRunModel(dataSource = "live", options = {}) {
|
|
|
51
50
|
totalCount: 0,
|
|
52
51
|
completedCount: 0,
|
|
53
52
|
phase: null,
|
|
54
|
-
filterActive: false,
|
|
55
|
-
filterQuery: "",
|
|
56
|
-
filterMatches: new Map(),
|
|
57
53
|
collapsedOverrides: new Map(),
|
|
58
54
|
selectedEntryId: null,
|
|
59
55
|
};
|
|
@@ -69,9 +65,6 @@ export function resetRunModel(model, dataSource = model.dataSource) {
|
|
|
69
65
|
model.totalCount = 0;
|
|
70
66
|
model.completedCount = 0;
|
|
71
67
|
model.phase = null;
|
|
72
|
-
model.filterActive = false;
|
|
73
|
-
model.filterQuery = "";
|
|
74
|
-
model.filterMatches = new Map();
|
|
75
68
|
model.collapsedOverrides = new Map();
|
|
76
69
|
model.selectedEntryId = null;
|
|
77
70
|
}
|
|
@@ -111,6 +104,7 @@ export function initModelFromPlans(model, servicePlans) {
|
|
|
111
104
|
diagnosis: null,
|
|
112
105
|
skipReason: null,
|
|
113
106
|
artifacts: [],
|
|
107
|
+
checkDetails: [],
|
|
114
108
|
});
|
|
115
109
|
}
|
|
116
110
|
}
|
|
@@ -175,6 +169,7 @@ export function applyArtifactToModel(model, artifact) {
|
|
|
175
169
|
diagnosis: fileResult.diagnosis || null,
|
|
176
170
|
skipReason: fileResult.reason || null,
|
|
177
171
|
artifacts: Array.isArray(fileResult.artifacts) ? fileResult.artifacts : [],
|
|
172
|
+
checkDetails: Array.isArray(fileResult.checkDetails) ? fileResult.checkDetails : [],
|
|
178
173
|
});
|
|
179
174
|
}
|
|
180
175
|
}
|
|
@@ -210,7 +205,10 @@ export function markFileFinished(model, task, outcome) {
|
|
|
210
205
|
file.status = "passed";
|
|
211
206
|
file.error = null;
|
|
212
207
|
file.failureDetails = [];
|
|
208
|
+
file.artifacts = Array.isArray(outcome.artifacts) ? outcome.artifacts : file.artifacts;
|
|
209
|
+
file.diagnosis = outcome.diagnosis || null;
|
|
213
210
|
}
|
|
211
|
+
file.checkDetails = Array.isArray(outcome.checkDetails) ? outcome.checkDetails : file.checkDetails;
|
|
214
212
|
file.durationMs = outcome.durationMs || null;
|
|
215
213
|
model.completedCount += 1;
|
|
216
214
|
if (file.status === "failed" && (!selectedEntry || selectedEntry.kind !== "file" || selectedEntry.status !== "failed")) {
|
|
@@ -277,23 +275,9 @@ export function finishModel(model, results, durationMs, regressionReport) {
|
|
|
277
275
|
model.selectedEntryId = findFirstFailureEntryId(model) || model.selectedEntryId || findFirstNavigableEntryId(model);
|
|
278
276
|
}
|
|
279
277
|
|
|
280
|
-
export function updateFilter(model, query) {
|
|
281
|
-
model.filterActive = true;
|
|
282
|
-
model.filterQuery = String(query || "");
|
|
283
|
-
model.filterMatches = new Map();
|
|
284
|
-
const normalizedQuery = model.filterQuery.trim();
|
|
285
|
-
if (!normalizedQuery) return;
|
|
286
|
-
for (const entry of collectAllEntries(model)) {
|
|
287
|
-
const match = matchRunTreeEntry(normalizedQuery, entry);
|
|
288
|
-
if (match.matched) {
|
|
289
|
-
model.filterMatches.set(entry.id, match);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
278
|
export function toggleCollapsed(model, entryId) {
|
|
295
279
|
const entry = getEntryById(model, entryId);
|
|
296
|
-
if (!entry || (entry.kind !== "type" && entry.kind !== "suite")) return;
|
|
280
|
+
if (!entry || (entry.kind !== "type" && entry.kind !== "suite" && entry.kind !== "file")) return;
|
|
297
281
|
const current = isCollapsed(model, entry);
|
|
298
282
|
model.collapsedOverrides.set(entryId, !current);
|
|
299
283
|
}
|
|
@@ -317,9 +301,6 @@ export function buildSnapshot(model) {
|
|
|
317
301
|
|| null;
|
|
318
302
|
const selectedEntryId = selectedEntry?.id || null;
|
|
319
303
|
const selectedFailure = toSelectedFailure(selectedEntry);
|
|
320
|
-
const filterResults = [...model.filterMatches.entries()]
|
|
321
|
-
.map(([id, match]) => ({ id, ...match }))
|
|
322
|
-
.sort((left, right) => right.score - left.score || left.id.localeCompare(right.id));
|
|
323
304
|
|
|
324
305
|
return {
|
|
325
306
|
dataSource: model.dataSource,
|
|
@@ -336,12 +317,6 @@ export function buildSnapshot(model) {
|
|
|
336
317
|
finished: model.finished,
|
|
337
318
|
summaryData: model.summaryData,
|
|
338
319
|
regressionCatalog: model.regressionCatalog,
|
|
339
|
-
filter: {
|
|
340
|
-
active: model.filterActive,
|
|
341
|
-
query: model.filterQuery,
|
|
342
|
-
results: filterResults,
|
|
343
|
-
count: filterResults.length,
|
|
344
|
-
},
|
|
345
320
|
runArtifact: model.runArtifact,
|
|
346
321
|
};
|
|
347
322
|
}
|
|
@@ -445,22 +420,18 @@ function buildNestedServices(model) {
|
|
|
445
420
|
|
|
446
421
|
function buildVisibleEntries(model, services) {
|
|
447
422
|
const entries = [];
|
|
448
|
-
const filterActive = model.filterActive && model.filterQuery.trim().length > 0;
|
|
449
|
-
const includedIds = filterActive ? buildFilteredIdSet(model, services) : null;
|
|
450
423
|
|
|
451
424
|
for (const service of services) {
|
|
452
|
-
pushVisibleEntry(entries, model, service, 0
|
|
425
|
+
pushVisibleEntry(entries, model, service, 0);
|
|
453
426
|
if (service.skipped) continue;
|
|
454
427
|
for (const typeNode of service.types) {
|
|
455
|
-
if (!pushVisibleEntry(entries, model, typeNode, 1
|
|
456
|
-
|
|
457
|
-
if (!typeExpanded) continue;
|
|
428
|
+
if (!pushVisibleEntry(entries, model, typeNode, 1)) continue;
|
|
429
|
+
if (typeNode.collapsed) continue;
|
|
458
430
|
for (const suite of typeNode.suites) {
|
|
459
|
-
if (!pushVisibleEntry(entries, model, suite, 2
|
|
460
|
-
|
|
461
|
-
if (!suiteExpanded) continue;
|
|
431
|
+
if (!pushVisibleEntry(entries, model, suite, 2)) continue;
|
|
432
|
+
if (suite.collapsed) continue;
|
|
462
433
|
for (const file of suite.files) {
|
|
463
|
-
pushVisibleEntry(entries, model, file, 3
|
|
434
|
+
pushVisibleEntry(entries, model, file, 3);
|
|
464
435
|
}
|
|
465
436
|
}
|
|
466
437
|
}
|
|
@@ -469,62 +440,12 @@ function buildVisibleEntries(model, services) {
|
|
|
469
440
|
return entries;
|
|
470
441
|
}
|
|
471
442
|
|
|
472
|
-
function pushVisibleEntry(entries, model, entry, depth
|
|
473
|
-
|
|
474
|
-
const base = toPublicEntry(entry, depth);
|
|
475
|
-
const match = model.filterMatches.get(entry.id) || null;
|
|
476
|
-
entries.push({
|
|
477
|
-
...base,
|
|
478
|
-
match,
|
|
479
|
-
});
|
|
443
|
+
function pushVisibleEntry(entries, model, entry, depth) {
|
|
444
|
+
entries.push(toPublicEntry(entry, depth, model));
|
|
480
445
|
return true;
|
|
481
446
|
}
|
|
482
447
|
|
|
483
|
-
function
|
|
484
|
-
const included = new Set();
|
|
485
|
-
const childrenById = new Map();
|
|
486
|
-
const parentsById = new Map();
|
|
487
|
-
|
|
488
|
-
for (const service of services) {
|
|
489
|
-
childrenById.set(service.id, service.types.map((entry) => entry.id));
|
|
490
|
-
for (const typeNode of service.types) {
|
|
491
|
-
parentsById.set(typeNode.id, service.id);
|
|
492
|
-
childrenById.set(typeNode.id, typeNode.suites.map((entry) => entry.id));
|
|
493
|
-
for (const suite of typeNode.suites) {
|
|
494
|
-
parentsById.set(suite.id, typeNode.id);
|
|
495
|
-
childrenById.set(suite.id, suite.files.map((entry) => entry.id));
|
|
496
|
-
for (const file of suite.files) {
|
|
497
|
-
parentsById.set(file.id, suite.id);
|
|
498
|
-
childrenById.set(file.id, []);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
for (const entryId of model.filterMatches.keys()) {
|
|
505
|
-
included.add(entryId);
|
|
506
|
-
let current = parentsById.get(entryId) || null;
|
|
507
|
-
while (current) {
|
|
508
|
-
included.add(current);
|
|
509
|
-
current = parentsById.get(current) || null;
|
|
510
|
-
}
|
|
511
|
-
const entry = getEntryById(model, entryId);
|
|
512
|
-
if (entry && entry.kind !== "file") {
|
|
513
|
-
includeDescendants(entryId, childrenById, included);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
return included;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
function includeDescendants(entryId, childrenById, included) {
|
|
521
|
-
for (const childId of childrenById.get(entryId) || []) {
|
|
522
|
-
included.add(childId);
|
|
523
|
-
includeDescendants(childId, childrenById, included);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function toPublicEntry(entry, depth) {
|
|
448
|
+
function toPublicEntry(entry, depth, model = null) {
|
|
528
449
|
if (entry.kind === "service") {
|
|
529
450
|
return {
|
|
530
451
|
id: entry.id,
|
|
@@ -567,6 +488,7 @@ function toPublicEntry(entry, depth) {
|
|
|
567
488
|
framework: entry.framework,
|
|
568
489
|
};
|
|
569
490
|
}
|
|
491
|
+
const autoCollapsed = defaultAutoCollapsed(entry);
|
|
570
492
|
return {
|
|
571
493
|
id: entry.id,
|
|
572
494
|
kind: "file",
|
|
@@ -586,6 +508,8 @@ function toPublicEntry(entry, depth) {
|
|
|
586
508
|
diagnosis: entry.diagnosis,
|
|
587
509
|
skipReason: entry.skipReason,
|
|
588
510
|
artifacts: entry.artifacts,
|
|
511
|
+
checkDetails: entry.checkDetails,
|
|
512
|
+
collapsed: model ? isCollapsed(model, { id: entry.id, autoCollapsed }) : autoCollapsed,
|
|
589
513
|
};
|
|
590
514
|
}
|
|
591
515
|
|
|
@@ -625,7 +549,12 @@ function isCollapsed(model, entry) {
|
|
|
625
549
|
if (model.collapsedOverrides.has(entry.id)) {
|
|
626
550
|
return Boolean(model.collapsedOverrides.get(entry.id));
|
|
627
551
|
}
|
|
628
|
-
return Boolean(entry.autoCollapsed);
|
|
552
|
+
return Boolean(entry.autoCollapsed ?? defaultAutoCollapsed(entry));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function defaultAutoCollapsed(entry) {
|
|
556
|
+
if (entry?.kind !== "file") return false;
|
|
557
|
+
return entry.status === "passed" || entry.status === "skipped" || entry.status === "pending";
|
|
629
558
|
}
|
|
630
559
|
|
|
631
560
|
function summarizeFiles(files) {
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
setRegressionCatalog,
|
|
18
18
|
setTotalFileCount,
|
|
19
19
|
toggleCollapsed,
|
|
20
|
-
updateFilter,
|
|
21
20
|
} from "./model.mjs";
|
|
22
21
|
|
|
23
22
|
export function createRunState({ dataSource = "live", autoCollapsePassedTreeBranches = true } = {}) {
|
|
@@ -143,27 +142,6 @@ export function createRunState({ dataSource = "live", autoCollapsePassedTreeBran
|
|
|
143
142
|
notify();
|
|
144
143
|
},
|
|
145
144
|
|
|
146
|
-
activateFilter() {
|
|
147
|
-
model.filterActive = true;
|
|
148
|
-
notify();
|
|
149
|
-
},
|
|
150
|
-
|
|
151
|
-
updateFilterQuery(query) {
|
|
152
|
-
updateFilter(model, query);
|
|
153
|
-
const snapshot = buildSnapshot(model);
|
|
154
|
-
if (snapshot.filter.results.length > 0) {
|
|
155
|
-
model.selectedEntryId = snapshot.filter.results[0].id;
|
|
156
|
-
}
|
|
157
|
-
notify();
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
deactivateFilter() {
|
|
161
|
-
model.filterActive = false;
|
|
162
|
-
model.filterQuery = "";
|
|
163
|
-
model.filterMatches = new Map();
|
|
164
|
-
notify();
|
|
165
|
-
},
|
|
166
|
-
|
|
167
145
|
revealFile(serviceName, filePath) {
|
|
168
146
|
const entryId = findEntryIdForFile(model, serviceName, filePath);
|
|
169
147
|
if (!entryId) return false;
|
package/lib/config/discovery.mjs
CHANGED
|
@@ -16,7 +16,6 @@ const DISCOVERY_RULES = [
|
|
|
16
16
|
{ suffix: ".dal.testkit.ts", type: "dal", framework: "k6" },
|
|
17
17
|
{ suffix: ".load.testkit.ts", type: "load", framework: "k6" },
|
|
18
18
|
{ suffix: ".ui.testkit.ts", type: "ui", framework: "playwright" },
|
|
19
|
-
{ suffix: ".pw.testkit.ts", type: "ui", framework: "playwright", legacySuffix: true },
|
|
20
19
|
];
|
|
21
20
|
|
|
22
21
|
export function discoverProject(productDir, explicitServices = {}, options = {}) {
|
|
@@ -31,15 +30,6 @@ export function discoverProject(productDir, explicitServices = {}, options = {})
|
|
|
31
30
|
for (const filePath of suiteFiles) {
|
|
32
31
|
const rule = inferRule(filePath);
|
|
33
32
|
if (!rule) continue;
|
|
34
|
-
if (rule.legacySuffix) {
|
|
35
|
-
diagnostics.push({
|
|
36
|
-
code: "legacy_ui_suffix",
|
|
37
|
-
severity: "warning",
|
|
38
|
-
message: `Legacy UI test suffix ".pw.testkit.ts" is deprecated. Rename to ".ui.testkit.ts": ${filePath}`,
|
|
39
|
-
path: filePath,
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
33
|
const owners = inferOwners(filePath, explicitServices, repoDiscovery);
|
|
44
34
|
if (owners === null) continue;
|
|
45
35
|
if (owners.length === 0) {
|
package/lib/discovery/index.mjs
CHANGED
|
@@ -489,7 +489,7 @@ function normalizePath(filePath) {
|
|
|
489
489
|
export function fileDisplayName(filePath) {
|
|
490
490
|
const base = path.posix
|
|
491
491
|
.basename(filePath)
|
|
492
|
-
.replace(/(\.int|\.e2e|\.scenario|\.dal|\.load|\.ui
|
|
492
|
+
.replace(/(\.int|\.e2e|\.scenario|\.dal|\.load|\.ui)\.testkit\.ts$/, "");
|
|
493
493
|
return formatDisplayName(base);
|
|
494
494
|
}
|
|
495
495
|
|
|
@@ -2,9 +2,6 @@ export const TEST_TYPE_ORDER = ["ui", "e2e", "scenario", "int", "dal", "load"];
|
|
|
2
2
|
export const TEST_TYPES = new Set(TEST_TYPE_ORDER);
|
|
3
3
|
export const RUN_TYPE_ORDER = [...TEST_TYPE_ORDER, "all"];
|
|
4
4
|
export const RUN_TYPES = new Set(RUN_TYPE_ORDER);
|
|
5
|
-
export const LEGACY_TEST_TYPE_ALIASES = new Map([
|
|
6
|
-
["pw", "ui"],
|
|
7
|
-
]);
|
|
8
5
|
|
|
9
6
|
const TEST_TYPE_LABELS = {
|
|
10
7
|
ui: "UI",
|
|
@@ -15,28 +12,22 @@ const TEST_TYPE_LABELS = {
|
|
|
15
12
|
load: "Load",
|
|
16
13
|
};
|
|
17
14
|
|
|
18
|
-
export function normalizePublicTestType(value) {
|
|
19
|
-
const normalized = String(value || "").trim();
|
|
20
|
-
return LEGACY_TEST_TYPE_ALIASES.get(normalized) || normalized;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
15
|
export function isPublicTestType(value) {
|
|
24
|
-
return TEST_TYPES.has(
|
|
16
|
+
return TEST_TYPES.has(String(value || "").trim());
|
|
25
17
|
}
|
|
26
18
|
|
|
27
19
|
export function isRunType(value) {
|
|
28
|
-
const normalized =
|
|
20
|
+
const normalized = String(value || "").trim();
|
|
29
21
|
return normalized === "all" || TEST_TYPES.has(normalized);
|
|
30
22
|
}
|
|
31
23
|
|
|
32
24
|
export function formatPublicTestType(value) {
|
|
33
|
-
const normalized =
|
|
25
|
+
const normalized = String(value || "").trim();
|
|
34
26
|
return TEST_TYPE_LABELS[normalized] || String(value || "");
|
|
35
27
|
}
|
|
36
28
|
|
|
37
|
-
export function publicTestTypeList({ includeAll = false
|
|
38
|
-
|
|
39
|
-
return includeLegacy ? [...values, "pw"] : [...values];
|
|
29
|
+
export function publicTestTypeList({ includeAll = false } = {}) {
|
|
30
|
+
return includeAll ? [...RUN_TYPE_ORDER] : [...TEST_TYPE_ORDER];
|
|
40
31
|
}
|
|
41
32
|
|
|
42
33
|
export function publicTestTypeListText(options = {}) {
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
determineDefaultRuntimeFailure,
|
|
13
13
|
extractDefaultRuntimeFatalDetail,
|
|
14
14
|
} from "./default-runtime-errors.mjs";
|
|
15
|
-
import { collectFailureDetailsFromRuntimeArtifacts } from "./failure-details.mjs";
|
|
15
|
+
import { collectFailureDetailsFromRuntimeArtifacts, collectCheckDetailsFromRuntimeArtifacts } from "./failure-details.mjs";
|
|
16
16
|
import { RUNTIME_ARTIFACT_MARKER } from "../runtime-src/k6/artifacts.js";
|
|
17
17
|
import { readDatabaseUrl } from "./state-io.mjs";
|
|
18
18
|
import { buildTaskExecutionEnv } from "./template.mjs";
|
|
@@ -147,6 +147,7 @@ export async function runDefaultRuntimeTask(
|
|
|
147
147
|
: null,
|
|
148
148
|
]);
|
|
149
149
|
const failureDetails = collectFailureDetailsFromRuntimeArtifacts(rawRuntimeArtifacts);
|
|
150
|
+
const checkDetails = collectCheckDetailsFromRuntimeArtifacts(rawRuntimeArtifacts);
|
|
150
151
|
const fatalRuntimeDetail = extractDefaultRuntimeFatalDetail(result.stderr || "", getFirstLine);
|
|
151
152
|
if (fatalRuntimeDetail && !failureDetails.some((detail) => detail?.kind === "runtime-exception")) {
|
|
152
153
|
failureDetails.unshift(fatalRuntimeDetail);
|
|
@@ -165,6 +166,7 @@ export async function runDefaultRuntimeTask(
|
|
|
165
166
|
finishedAt,
|
|
166
167
|
artifacts: [...runtimeArtifacts, ...outputArtifacts],
|
|
167
168
|
failureDetails,
|
|
169
|
+
checkDetails,
|
|
168
170
|
};
|
|
169
171
|
}
|
|
170
172
|
|
|
@@ -90,6 +90,28 @@ export function collectFailureDetailsFromRuntimeArtifacts(artifacts) {
|
|
|
90
90
|
return mergeFailureDetails(details);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
export function collectCheckDetailsFromRuntimeArtifacts(artifacts) {
|
|
94
|
+
const checks = [];
|
|
95
|
+
|
|
96
|
+
for (const artifact of Array.isArray(artifacts) ? artifacts : []) {
|
|
97
|
+
if (artifact?.kind !== "testkit.checks") continue;
|
|
98
|
+
for (const check of Array.isArray(artifact?.data?.checks) ? artifact.data.checks : []) {
|
|
99
|
+
const name = normalizeNonEmptyString(check?.name) || "unnamed";
|
|
100
|
+
const passes = Math.max(0, Number(check?.passes) || 0);
|
|
101
|
+
const fails = Math.max(0, Number(check?.fails) || 0);
|
|
102
|
+
checks.push({
|
|
103
|
+
name,
|
|
104
|
+
path: normalizeStringArray(check?.path),
|
|
105
|
+
passes,
|
|
106
|
+
fails,
|
|
107
|
+
passed: passes > 0 && fails === 0,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return checks;
|
|
113
|
+
}
|
|
114
|
+
|
|
93
115
|
function normalizeStringArray(value) {
|
|
94
116
|
if (!Array.isArray(value)) return [];
|
|
95
117
|
return value
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
const ASSISTANT_SESSION_ENV = "TESTKIT_ASSISTANT_SESSION_ID";
|
|
2
|
+
const ASSISTANT_TURN_ENV = "TESTKIT_ASSISTANT_TURN_ID";
|
|
2
3
|
const ASSISTANT_COMMAND_ID_ENV = "TESTKIT_ASSISTANT_COMMAND_ID";
|
|
3
4
|
|
|
4
5
|
export function buildRunProvenance(env = process.env) {
|
|
5
6
|
const sessionId = normalizeOptionalString(env?.[ASSISTANT_SESSION_ENV]);
|
|
7
|
+
const turnId = normalizeOptionalString(env?.[ASSISTANT_TURN_ENV]);
|
|
6
8
|
const commandId = normalizeOptionalString(env?.[ASSISTANT_COMMAND_ID_ENV]);
|
|
7
|
-
if (!sessionId && !commandId) return null;
|
|
9
|
+
if (!sessionId && !turnId && !commandId) return null;
|
|
8
10
|
return {
|
|
9
11
|
assistant: {
|
|
10
12
|
sessionId,
|
|
13
|
+
turnId,
|
|
11
14
|
commandId,
|
|
12
15
|
},
|
|
13
16
|
};
|
package/lib/runner/results.mjs
CHANGED
|
@@ -48,6 +48,7 @@ export function buildServiceTrackers(servicePlans, startedAt) {
|
|
|
48
48
|
status: "not_run",
|
|
49
49
|
artifacts: [],
|
|
50
50
|
failureDetails: [],
|
|
51
|
+
checkDetails: [],
|
|
51
52
|
},
|
|
52
53
|
];
|
|
53
54
|
}),
|
|
@@ -62,6 +63,7 @@ export function buildServiceTrackers(servicePlans, startedAt) {
|
|
|
62
63
|
status: "skipped",
|
|
63
64
|
artifacts: [],
|
|
64
65
|
failureDetails: [],
|
|
66
|
+
checkDetails: [],
|
|
65
67
|
},
|
|
66
68
|
]),
|
|
67
69
|
]),
|
|
@@ -125,6 +127,7 @@ export function recordTaskOutcome(trackers, task, outcome, finishedAt = Date.now
|
|
|
125
127
|
existingFileResult.status = status;
|
|
126
128
|
existingFileResult.artifacts = Array.isArray(outcome.artifacts) ? outcome.artifacts : [];
|
|
127
129
|
existingFileResult.failureDetails = mergeFailureDetails(outcome.failureDetails);
|
|
130
|
+
existingFileResult.checkDetails = normalizeCheckDetails(outcome.checkDetails);
|
|
128
131
|
} else {
|
|
129
132
|
suite.fileResultsByPath.set(normalizedPath, {
|
|
130
133
|
path: normalizedPath,
|
|
@@ -135,6 +138,7 @@ export function recordTaskOutcome(trackers, task, outcome, finishedAt = Date.now
|
|
|
135
138
|
status,
|
|
136
139
|
artifacts: Array.isArray(outcome.artifacts) ? outcome.artifacts : [],
|
|
137
140
|
failureDetails: mergeFailureDetails(outcome.failureDetails),
|
|
141
|
+
checkDetails: normalizeCheckDetails(outcome.checkDetails),
|
|
138
142
|
});
|
|
139
143
|
}
|
|
140
144
|
if (status === "failed" && !suite.failedFileSet.has(task.file)) {
|
|
@@ -258,6 +262,9 @@ function finalizeSuite(suite) {
|
|
|
258
262
|
...(Array.isArray(file.artifacts) && file.artifacts.length > 0
|
|
259
263
|
? { artifacts: file.artifacts }
|
|
260
264
|
: {}),
|
|
265
|
+
...(Array.isArray(file.checkDetails) && file.checkDetails.length > 0
|
|
266
|
+
? { checkDetails: file.checkDetails }
|
|
267
|
+
: {}),
|
|
261
268
|
}));
|
|
262
269
|
|
|
263
270
|
return {
|
|
@@ -289,3 +296,27 @@ function normalizeOutcomeStatus(outcome) {
|
|
|
289
296
|
if (outcome?.status === "skipped") return "skipped";
|
|
290
297
|
return outcome?.failed ? "failed" : "passed";
|
|
291
298
|
}
|
|
299
|
+
|
|
300
|
+
function normalizeCheckDetails(value) {
|
|
301
|
+
if (!Array.isArray(value)) return [];
|
|
302
|
+
return value.map((check) => {
|
|
303
|
+
const passes = normalizeCount(check?.passes);
|
|
304
|
+
const fails = normalizeCount(check?.fails);
|
|
305
|
+
return {
|
|
306
|
+
name: typeof check?.name === "string" && check.name.trim().length > 0 ? check.name.trim() : "unnamed",
|
|
307
|
+
path: Array.isArray(check?.path)
|
|
308
|
+
? check.path
|
|
309
|
+
.map((entry) => String(entry || "").trim())
|
|
310
|
+
.filter(Boolean)
|
|
311
|
+
: [],
|
|
312
|
+
passes,
|
|
313
|
+
fails,
|
|
314
|
+
passed: passes > 0 && fails === 0,
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function normalizeCount(value) {
|
|
320
|
+
const count = Number(value);
|
|
321
|
+
return Number.isFinite(count) && count > 0 ? count : 0;
|
|
322
|
+
}
|
|
@@ -242,13 +242,21 @@ export function collectBundleCleanupTargets(productDir, { allConfigs = [], servi
|
|
|
242
242
|
export function collectAssistantCleanupTargets(productDir) {
|
|
243
243
|
const now = Date.now();
|
|
244
244
|
const dir = path.join(productDir, ".testkit", "assistant", "sessions");
|
|
245
|
-
return
|
|
246
|
-
.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
245
|
+
return listDirectories(dir)
|
|
246
|
+
.map((sessionDir) => {
|
|
247
|
+
const files = listFiles(sessionDir);
|
|
248
|
+
const sizeBytes = files.reduce((sum, file) => sum + file.size, 0);
|
|
249
|
+
const newestMtimeMs = files.reduce((latest, file) => Math.max(latest, file.mtimeMs), 0);
|
|
250
|
+
const expired = newestMtimeMs > 0 && now - newestMtimeMs >= ASSISTANT_RESULT_TTL_MS;
|
|
251
|
+
const large = sizeBytes >= ASSISTANT_LARGE_RESULT_BYTES;
|
|
252
|
+
if (!expired && !large) return null;
|
|
253
|
+
return {
|
|
254
|
+
path: sessionDir,
|
|
255
|
+
reason: large ? "large" : "expired",
|
|
256
|
+
sizeBytes,
|
|
257
|
+
};
|
|
258
|
+
})
|
|
259
|
+
.filter(Boolean);
|
|
252
260
|
}
|
|
253
261
|
|
|
254
262
|
function listDirectories(dir) {
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
RUN_TYPES,
|
|
4
4
|
TEST_TYPE_ORDER,
|
|
5
5
|
TEST_TYPES,
|
|
6
|
-
normalizePublicTestType,
|
|
7
6
|
publicTestTypeListText,
|
|
8
7
|
} from "../domain/test-types.mjs";
|
|
9
8
|
|
|
@@ -14,7 +13,7 @@ export function normalizeTypeValues(values = []) {
|
|
|
14
13
|
for (const part of String(rawValue).split(",")) {
|
|
15
14
|
const value = part.trim();
|
|
16
15
|
if (!value) continue;
|
|
17
|
-
const normalized =
|
|
16
|
+
const normalized = value;
|
|
18
17
|
if (!RUN_TYPES.has(normalized)) {
|
|
19
18
|
throw new Error(
|
|
20
19
|
`Unknown type "${value}". Expected one of: ${publicTestTypeListText({ includeAll: true })}.`
|
|
@@ -57,7 +56,7 @@ export function parseSuiteSelectors(values = []) {
|
|
|
57
56
|
|
|
58
57
|
const type = typeMatch[1];
|
|
59
58
|
const name = typeMatch[2].trim();
|
|
60
|
-
const normalizedType =
|
|
59
|
+
const normalizedType = type.trim();
|
|
61
60
|
if (!TEST_TYPES.has(normalizedType)) {
|
|
62
61
|
throw new Error(
|
|
63
62
|
`Unknown suite selector type "${type}". Expected one of: ${publicTestTypeListText()}.`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.113",
|
|
4
4
|
"description": "Browser bridge helpers for testkit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@elench/testkit-protocol": "0.1.
|
|
25
|
+
"@elench/testkit-protocol": "0.1.113"
|
|
26
26
|
},
|
|
27
27
|
"private": false
|
|
28
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.113",
|
|
4
4
|
"description": "Assistant-first CLI for running, inspecting, and debugging local testkit suites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -90,10 +90,10 @@
|
|
|
90
90
|
},
|
|
91
91
|
"dependencies": {
|
|
92
92
|
"@babel/code-frame": "^7.29.0",
|
|
93
|
-
"@elench/next-analysis": "0.1.
|
|
94
|
-
"@elench/testkit-bridge": "0.1.
|
|
95
|
-
"@elench/testkit-protocol": "0.1.
|
|
96
|
-
"@elench/ts-analysis": "0.1.
|
|
93
|
+
"@elench/next-analysis": "0.1.113",
|
|
94
|
+
"@elench/testkit-bridge": "0.1.113",
|
|
95
|
+
"@elench/testkit-protocol": "0.1.113",
|
|
96
|
+
"@elench/ts-analysis": "0.1.113",
|
|
97
97
|
"@oclif/core": "^4.10.6",
|
|
98
98
|
"@playwright/test": "^1.52.0",
|
|
99
99
|
"esbuild": "^0.25.11",
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import React, { createElement } from "react";
|
|
2
|
-
import { Text } from "ink";
|
|
3
|
-
import { bold, dim } from "../../terminal/colors.mjs";
|
|
4
|
-
|
|
5
|
-
export function FilterBar({ filter } = {}) {
|
|
6
|
-
if (!filter?.active) return null;
|
|
7
|
-
return createElement(
|
|
8
|
-
Text,
|
|
9
|
-
null,
|
|
10
|
-
`${bold("/")}${filter.query}${dim(` ${filter.count} ${filter.count === 1 ? "match" : "matches"}`)}`
|
|
11
|
-
);
|
|
12
|
-
}
|