donobu 5.29.0 → 5.30.0
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/dist/esm/lib/test/testExtension.d.ts +31 -1
- package/dist/esm/lib/test/testExtension.js +273 -0
- package/dist/esm/managers/DonobuStack.js +1 -1
- package/dist/esm/managers/TestsManager.d.ts +2 -2
- package/dist/esm/managers/TestsManager.js +10 -0
- package/dist/esm/models/PaginatedResult.d.ts +15 -0
- package/dist/esm/models/PaginatedResult.js +13 -2
- package/dist/esm/models/TestMetadata.d.ts +630 -0
- package/dist/esm/models/TestMetadata.js +10 -1
- package/dist/esm/persistence/tests/TestsPersistence.d.ts +2 -2
- package/dist/esm/persistence/tests/TestsPersistenceDonobuApi.d.ts +3 -3
- package/dist/esm/persistence/tests/TestsPersistenceDonobuApi.js +18 -2
- package/dist/esm/persistence/tests/TestsPersistenceRegistry.d.ts +9 -1
- package/dist/esm/persistence/tests/TestsPersistenceRegistry.js +9 -2
- package/dist/esm/persistence/tests/TestsPersistenceSqlite.d.ts +2 -1
- package/dist/esm/persistence/tests/TestsPersistenceSqlite.js +65 -6
- package/dist/esm/persistence/tests/TestsPersistenceVolatile.d.ts +22 -3
- package/dist/esm/persistence/tests/TestsPersistenceVolatile.js +69 -4
- package/dist/esm/reporter/renderMarkdown.js +1 -1
- package/dist/lib/test/testExtension.d.ts +31 -1
- package/dist/lib/test/testExtension.js +273 -0
- package/dist/managers/DonobuStack.js +1 -1
- package/dist/managers/TestsManager.d.ts +2 -2
- package/dist/managers/TestsManager.js +10 -0
- package/dist/models/PaginatedResult.d.ts +15 -0
- package/dist/models/PaginatedResult.js +13 -2
- package/dist/models/TestMetadata.d.ts +630 -0
- package/dist/models/TestMetadata.js +10 -1
- package/dist/persistence/tests/TestsPersistence.d.ts +2 -2
- package/dist/persistence/tests/TestsPersistenceDonobuApi.d.ts +3 -3
- package/dist/persistence/tests/TestsPersistenceDonobuApi.js +18 -2
- package/dist/persistence/tests/TestsPersistenceRegistry.d.ts +9 -1
- package/dist/persistence/tests/TestsPersistenceRegistry.js +9 -2
- package/dist/persistence/tests/TestsPersistenceSqlite.d.ts +2 -1
- package/dist/persistence/tests/TestsPersistenceSqlite.js +65 -6
- package/dist/persistence/tests/TestsPersistenceVolatile.d.ts +22 -3
- package/dist/persistence/tests/TestsPersistenceVolatile.js +69 -4
- package/dist/reporter/renderMarkdown.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PlaywrightWorkerOptions } from '@playwright/test';
|
|
1
|
+
import type { PlaywrightWorkerOptions, TestInfo } from '@playwright/test';
|
|
2
2
|
import type { GptClient } from '../../clients/GptClient';
|
|
3
3
|
import type { BrowserStorageState } from '../../models/BrowserStorageState';
|
|
4
4
|
import { FlowLogBuffer } from '../../utils/FlowLogBuffer';
|
|
@@ -9,6 +9,7 @@ export declare const test: import("@playwright/test").TestType<import("@playwrig
|
|
|
9
9
|
flowId: string;
|
|
10
10
|
logBuffer: FlowLogBuffer;
|
|
11
11
|
};
|
|
12
|
+
pwApiStepLogger: void;
|
|
12
13
|
storageState?: BrowserStorageState | Promise<BrowserStorageState>;
|
|
13
14
|
gptClient?: GptClient;
|
|
14
15
|
page: DonobuExtendedPage;
|
|
@@ -26,4 +27,33 @@ export declare const test: import("@playwright/test").TestType<import("@playwrig
|
|
|
26
27
|
*/
|
|
27
28
|
donobuFileUploadDrainGuard: void;
|
|
28
29
|
}>;
|
|
30
|
+
export declare function emitPlaywrightStepLog(step: any): void;
|
|
31
|
+
/** Reset the source-line cache. Test-only. */
|
|
32
|
+
export declare function _resetSourceLineCacheForTests(): void;
|
|
33
|
+
/**
|
|
34
|
+
* Monkey-patch `testInfo._callbacks.onStepEnd` so each Playwright step in
|
|
35
|
+
* the `pw:api` / `expect` categories emits an `appLogger.info` entry once
|
|
36
|
+
* Playwright signals the step has finished. Returns a teardown function
|
|
37
|
+
* that restores the original callback.
|
|
38
|
+
*
|
|
39
|
+
* Why patch `onStepEnd` and not `_addStep`:
|
|
40
|
+
* Playwright captures the step's source location with a stack walk inside
|
|
41
|
+
* `_addStep` (see playwright/lib/worker/testInfo.js). Patching `_addStep`
|
|
42
|
+
* inserts a Donobu library frame into that stack, and Playwright's
|
|
43
|
+
* built-in filter only excludes `@playwright/test` / `playwright-core`
|
|
44
|
+
* paths — so for steps that don't pre-set `data.location` (most notably
|
|
45
|
+
* `expect`), the captured location lands inside our wrapper in
|
|
46
|
+
* `node_modules/donobu/...`. The downstream node_modules filter would
|
|
47
|
+
* then drop the entry. `_callbacks.onStepEnd` runs AFTER the stack
|
|
48
|
+
* capture, so this hook is invisible to Playwright's location resolution.
|
|
49
|
+
*
|
|
50
|
+
* Verified against @playwright/test 1.57+. `_callbacks` / `_stepMap` and
|
|
51
|
+
* the step shape (category, title, params, endWallTime, error, location)
|
|
52
|
+
* are private Playwright internals — install and per-step logging are
|
|
53
|
+
* wrapped in try/catch so any shape drift degrades to a no-op rather than
|
|
54
|
+
* failing the test.
|
|
55
|
+
*
|
|
56
|
+
* Exported for unit testing.
|
|
57
|
+
*/
|
|
58
|
+
export declare function installPlaywrightStepLogger(testInfo: TestInfo): () => void;
|
|
29
59
|
//# sourceMappingURL=testExtension.d.ts.map
|
|
@@ -18,6 +18,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
20
|
exports.test = void 0;
|
|
21
|
+
exports.emitPlaywrightStepLog = emitPlaywrightStepLog;
|
|
22
|
+
exports._resetSourceLineCacheForTests = _resetSourceLineCacheForTests;
|
|
23
|
+
exports.installPlaywrightStepLogger = installPlaywrightStepLogger;
|
|
21
24
|
const test_1 = require("@playwright/test");
|
|
22
25
|
const async_hooks_1 = require("async_hooks");
|
|
23
26
|
const crypto_1 = require("crypto");
|
|
@@ -28,6 +31,7 @@ const v4_1 = require("zod/v4");
|
|
|
28
31
|
const envVars_1 = require("../../envVars");
|
|
29
32
|
const DonobuFlowsManager_1 = require("../../managers/DonobuFlowsManager");
|
|
30
33
|
const fileUploadWorkerRegistry_1 = require("../../persistence/files/fileUploadWorkerRegistry");
|
|
34
|
+
const ansi_1 = require("../../utils/ansi");
|
|
31
35
|
const BrowserUtils_1 = require("../../utils/BrowserUtils");
|
|
32
36
|
const FlowLogBuffer_1 = require("../../utils/FlowLogBuffer");
|
|
33
37
|
const Logger_1 = require("../../utils/Logger");
|
|
@@ -257,6 +261,36 @@ exports.test = test_1.test.extend({
|
|
|
257
261
|
},
|
|
258
262
|
{ scope: 'test', auto: true },
|
|
259
263
|
],
|
|
264
|
+
/**
|
|
265
|
+
* Auto fixture that monkey-patches `testInfo._addStep` so each Playwright
|
|
266
|
+
* step in the `pw:api` (clicks, fills, navigations) and `expect`
|
|
267
|
+
* (assertions) categories emits an `appLogger.info` entry on completion.
|
|
268
|
+
* Routing native Playwright operations through the same per-flow
|
|
269
|
+
* `FlowLogBuffer` that AI tool-call logs populate makes the live
|
|
270
|
+
* `/api/flows/:id/logs` feed and persisted `logs.json` capture meaningful
|
|
271
|
+
* step-by-step data even for non-AI Playwright tests.
|
|
272
|
+
*
|
|
273
|
+
* Depends on `flowLoggingContext` so the ALS / `processLocalLogBuffer` are
|
|
274
|
+
* already set up by the time our patched `complete` fires.
|
|
275
|
+
*
|
|
276
|
+
* Verified against @playwright/test 1.57. `_addStep` and the TestStepImpl
|
|
277
|
+
* shape (category, title, params, wallTime, endWallTime, error, complete)
|
|
278
|
+
* are private Playwright internals — install and per-step logging are
|
|
279
|
+
* wrapped in try/catch so any shape drift degrades to a no-op rather than
|
|
280
|
+
* failing the test.
|
|
281
|
+
*/
|
|
282
|
+
pwApiStepLogger: [
|
|
283
|
+
async ({ flowLoggingContext: _ }, use, testInfo) => {
|
|
284
|
+
const teardown = installPlaywrightStepLogger(testInfo);
|
|
285
|
+
try {
|
|
286
|
+
await use();
|
|
287
|
+
}
|
|
288
|
+
finally {
|
|
289
|
+
teardown();
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
{ scope: 'test', auto: true },
|
|
293
|
+
],
|
|
260
294
|
/**
|
|
261
295
|
* Drain in-flight file uploads (videos, screenshots, etc.) to Donobu
|
|
262
296
|
* Cloud at the end of every Playwright worker's lifetime.
|
|
@@ -447,6 +481,245 @@ function collectNativeSteps(rawSteps) {
|
|
|
447
481
|
result.sort((a, b) => a.endWallTime - b.endWallTime);
|
|
448
482
|
return result;
|
|
449
483
|
}
|
|
484
|
+
// ---------------------------------------------------------------------------
|
|
485
|
+
// Live Playwright step → appLogger pipeline
|
|
486
|
+
// ---------------------------------------------------------------------------
|
|
487
|
+
// Used by the `pwApiStepLogger` fixture above to surface native Playwright
|
|
488
|
+
// operations (clicks, fills, navigations, expects) in the same per-flow
|
|
489
|
+
// FlowLogBuffer that AI tool-call logs already populate.
|
|
490
|
+
const LOGGED_STEP_CATEGORIES = new Set([
|
|
491
|
+
'pw:api',
|
|
492
|
+
'expect',
|
|
493
|
+
]);
|
|
494
|
+
const STEP_PARAM_ALLOWLIST = [
|
|
495
|
+
'url',
|
|
496
|
+
'selector',
|
|
497
|
+
'text',
|
|
498
|
+
'value',
|
|
499
|
+
'timeout',
|
|
500
|
+
'name',
|
|
501
|
+
'key',
|
|
502
|
+
// For expect steps Playwright stores the asserted value here
|
|
503
|
+
// (e.g., `expect(x).toBe('$51.00')` → params: { expected: '$51.00' }).
|
|
504
|
+
'expected',
|
|
505
|
+
];
|
|
506
|
+
/**
|
|
507
|
+
* Pull a small, JSON-safe subset of `step.params` for logging. Playwright's
|
|
508
|
+
* step.params can hold non-serializable values (Locator handles, Page refs,
|
|
509
|
+
* etc.) that would either cycle or bloat the log feed; we keep only known
|
|
510
|
+
* primitive fields.
|
|
511
|
+
*/
|
|
512
|
+
function pickStepParams(rawParams) {
|
|
513
|
+
if (!rawParams || typeof rawParams !== 'object') {
|
|
514
|
+
return undefined;
|
|
515
|
+
}
|
|
516
|
+
const result = {};
|
|
517
|
+
let hasAny = false;
|
|
518
|
+
for (const key of STEP_PARAM_ALLOWLIST) {
|
|
519
|
+
const value = rawParams[key];
|
|
520
|
+
if (value === undefined) {
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
const t = typeof value;
|
|
524
|
+
if (t === 'string' || t === 'number' || t === 'boolean') {
|
|
525
|
+
result[key] = value;
|
|
526
|
+
hasAny = true;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return hasAny ? result : undefined;
|
|
530
|
+
}
|
|
531
|
+
const NODE_MODULES_LOCATION_REGEX = /[\\/]node_modules[\\/]/;
|
|
532
|
+
function emitPlaywrightStepLog(step) {
|
|
533
|
+
const category = step?.category ?? '';
|
|
534
|
+
if (!LOGGED_STEP_CATEGORIES.has(category)) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
// Drop steps that originated inside a third-party (or Donobu library)
|
|
538
|
+
// package. The user's test code never lives in node_modules, so any step
|
|
539
|
+
// attributed there is plumbing — Donobu's own AssertTool, internal CDP
|
|
540
|
+
// wrappers, etc. — not the user's intent.
|
|
541
|
+
const locationFile = (step.location &&
|
|
542
|
+
typeof step.location === 'object' &&
|
|
543
|
+
typeof (step.location.file ?? step.location.fileName) === 'string' &&
|
|
544
|
+
(step.location.file ?? step.location.fileName)) ||
|
|
545
|
+
'';
|
|
546
|
+
if (locationFile && NODE_MODULES_LOCATION_REGEX.test(locationFile)) {
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const title = step.title ?? '';
|
|
550
|
+
const startedAt = typeof step.wallTime === 'number' ? step.wallTime : undefined;
|
|
551
|
+
const completedAt = typeof step.endWallTime === 'number' ? step.endWallTime : undefined;
|
|
552
|
+
const durationMs = startedAt !== undefined && completedAt !== undefined
|
|
553
|
+
? completedAt - startedAt
|
|
554
|
+
: undefined;
|
|
555
|
+
const error = step.error && typeof step.error === 'object'
|
|
556
|
+
? {
|
|
557
|
+
name: typeof step.error.name === 'string' ? step.error.name : 'Error',
|
|
558
|
+
// Playwright matchers emit ANSI-colored diff output. Strip it so
|
|
559
|
+
// raw escape sequences don't leak into the log feed.
|
|
560
|
+
message: (0, ansi_1.stripAnsi)(typeof step.error.message === 'string' ? step.error.message : ''),
|
|
561
|
+
}
|
|
562
|
+
: undefined;
|
|
563
|
+
const location = step.location && typeof step.location === 'object'
|
|
564
|
+
? {
|
|
565
|
+
file: step.location.file ?? step.location.fileName ?? '',
|
|
566
|
+
line: step.location.line ?? 0,
|
|
567
|
+
column: step.location.column ?? 0,
|
|
568
|
+
}
|
|
569
|
+
: undefined;
|
|
570
|
+
const params = pickStepParams(step.params);
|
|
571
|
+
// Default message: Playwright's title verbatim, plus a primitive
|
|
572
|
+
// `expected` value for matchers Playwright didn't already inline.
|
|
573
|
+
let message = `${title}${formatExpectedSuffix(title, params?.expected)}`;
|
|
574
|
+
// For expect steps, prefer the actual user source line at
|
|
575
|
+
// location.file:line — `Expect "toBe"` is too bare; the source shows
|
|
576
|
+
// both the LHS being asserted and the matcher together
|
|
577
|
+
// (`expect(text).toBe('$51.01')`). Fall back to the matcher message if
|
|
578
|
+
// the source can't be read.
|
|
579
|
+
if (category === 'expect' && location?.file && location.line > 0) {
|
|
580
|
+
const sourceLine = readSourceLine(location.file, location.line);
|
|
581
|
+
if (sourceLine) {
|
|
582
|
+
message = sourceLine;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const meta = {
|
|
586
|
+
category,
|
|
587
|
+
title,
|
|
588
|
+
params,
|
|
589
|
+
durationMs,
|
|
590
|
+
startedAt,
|
|
591
|
+
completedAt,
|
|
592
|
+
error,
|
|
593
|
+
location,
|
|
594
|
+
};
|
|
595
|
+
// A populated `step.error` means Playwright reported the step as failed —
|
|
596
|
+
// surface it at error level so it stands out in the feed.
|
|
597
|
+
if (error) {
|
|
598
|
+
Logger_1.appLogger.error(message, meta);
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
Logger_1.appLogger.info(message, meta);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function formatExpectedSuffix(title, expected) {
|
|
605
|
+
if (expected === undefined || expected === null) {
|
|
606
|
+
return '';
|
|
607
|
+
}
|
|
608
|
+
const t = typeof expected;
|
|
609
|
+
if (t !== 'string' && t !== 'number' && t !== 'boolean') {
|
|
610
|
+
return '';
|
|
611
|
+
}
|
|
612
|
+
const formatted = JSON.stringify(expected);
|
|
613
|
+
if (title.includes(formatted)) {
|
|
614
|
+
return '';
|
|
615
|
+
}
|
|
616
|
+
return ` ${formatted}`;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Per-worker cache of source file contents (split into lines), keyed by
|
|
620
|
+
* absolute file path. Source files don't change during a worker's
|
|
621
|
+
* lifetime, so one read per file is sufficient.
|
|
622
|
+
*
|
|
623
|
+
* `null` means a previous read failed; we cache that to avoid retrying.
|
|
624
|
+
*/
|
|
625
|
+
const sourceLineCache = new Map();
|
|
626
|
+
/** Reset the source-line cache. Test-only. */
|
|
627
|
+
function _resetSourceLineCacheForTests() {
|
|
628
|
+
sourceLineCache.clear();
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Read the trimmed line at `file:line` (1-based), with a per-worker
|
|
632
|
+
* cache. Drops a trailing semicolon for cleanliness. Returns undefined
|
|
633
|
+
* if the file or line can't be read.
|
|
634
|
+
*/
|
|
635
|
+
function readSourceLine(file, line) {
|
|
636
|
+
let lines = sourceLineCache.get(file);
|
|
637
|
+
if (lines === undefined) {
|
|
638
|
+
try {
|
|
639
|
+
lines = (0, fs_1.readFileSync)(file, 'utf8').split('\n');
|
|
640
|
+
}
|
|
641
|
+
catch {
|
|
642
|
+
lines = null;
|
|
643
|
+
}
|
|
644
|
+
sourceLineCache.set(file, lines);
|
|
645
|
+
}
|
|
646
|
+
if (!lines) {
|
|
647
|
+
return undefined;
|
|
648
|
+
}
|
|
649
|
+
const raw = lines[line - 1];
|
|
650
|
+
if (typeof raw !== 'string') {
|
|
651
|
+
return undefined;
|
|
652
|
+
}
|
|
653
|
+
let trimmed = raw.trim();
|
|
654
|
+
if (trimmed.endsWith(';')) {
|
|
655
|
+
trimmed = trimmed.slice(0, -1);
|
|
656
|
+
}
|
|
657
|
+
return trimmed || undefined;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Monkey-patch `testInfo._callbacks.onStepEnd` so each Playwright step in
|
|
661
|
+
* the `pw:api` / `expect` categories emits an `appLogger.info` entry once
|
|
662
|
+
* Playwright signals the step has finished. Returns a teardown function
|
|
663
|
+
* that restores the original callback.
|
|
664
|
+
*
|
|
665
|
+
* Why patch `onStepEnd` and not `_addStep`:
|
|
666
|
+
* Playwright captures the step's source location with a stack walk inside
|
|
667
|
+
* `_addStep` (see playwright/lib/worker/testInfo.js). Patching `_addStep`
|
|
668
|
+
* inserts a Donobu library frame into that stack, and Playwright's
|
|
669
|
+
* built-in filter only excludes `@playwright/test` / `playwright-core`
|
|
670
|
+
* paths — so for steps that don't pre-set `data.location` (most notably
|
|
671
|
+
* `expect`), the captured location lands inside our wrapper in
|
|
672
|
+
* `node_modules/donobu/...`. The downstream node_modules filter would
|
|
673
|
+
* then drop the entry. `_callbacks.onStepEnd` runs AFTER the stack
|
|
674
|
+
* capture, so this hook is invisible to Playwright's location resolution.
|
|
675
|
+
*
|
|
676
|
+
* Verified against @playwright/test 1.57+. `_callbacks` / `_stepMap` and
|
|
677
|
+
* the step shape (category, title, params, endWallTime, error, location)
|
|
678
|
+
* are private Playwright internals — install and per-step logging are
|
|
679
|
+
* wrapped in try/catch so any shape drift degrades to a no-op rather than
|
|
680
|
+
* failing the test.
|
|
681
|
+
*
|
|
682
|
+
* Exported for unit testing.
|
|
683
|
+
*/
|
|
684
|
+
function installPlaywrightStepLogger(testInfo) {
|
|
685
|
+
const ti = testInfo;
|
|
686
|
+
const callbacks = ti._callbacks;
|
|
687
|
+
const stepMap = ti._stepMap;
|
|
688
|
+
const originalOnStepEnd = callbacks && typeof callbacks.onStepEnd === 'function'
|
|
689
|
+
? callbacks.onStepEnd
|
|
690
|
+
: null;
|
|
691
|
+
if (!callbacks || !originalOnStepEnd || !stepMap) {
|
|
692
|
+
return () => { };
|
|
693
|
+
}
|
|
694
|
+
let installed = false;
|
|
695
|
+
try {
|
|
696
|
+
callbacks.onStepEnd = function patchedOnStepEnd(payload) {
|
|
697
|
+
const ret = originalOnStepEnd.call(this, payload);
|
|
698
|
+
try {
|
|
699
|
+
const stepId = payload?.stepId;
|
|
700
|
+
if (typeof stepId === 'string') {
|
|
701
|
+
const step = stepMap.get(stepId);
|
|
702
|
+
if (step && LOGGED_STEP_CATEGORIES.has(step.category)) {
|
|
703
|
+
emitPlaywrightStepLog(step);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
catch (err) {
|
|
708
|
+
Logger_1.appLogger.debug('Failed to log Playwright step', err);
|
|
709
|
+
}
|
|
710
|
+
return ret;
|
|
711
|
+
};
|
|
712
|
+
installed = true;
|
|
713
|
+
}
|
|
714
|
+
catch (err) {
|
|
715
|
+
Logger_1.appLogger.warn('Failed to install Playwright step logger; native Playwright steps will not appear in flow logs', err);
|
|
716
|
+
}
|
|
717
|
+
return () => {
|
|
718
|
+
if (installed) {
|
|
719
|
+
callbacks.onStepEnd = originalOnStepEnd;
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
}
|
|
450
723
|
/**
|
|
451
724
|
* Fetch tool-call screenshots from the flow's persistence layer and attach
|
|
452
725
|
* them to the Playwright report. Each screenshot is attached as
|
|
@@ -49,7 +49,7 @@ async function setupDonobuStack(donobuDeploymentEnvironment, controlPanelFactory
|
|
|
49
49
|
const envPersistenceFactory = await EnvPersistenceRegistry_1.EnvPersistenceRegistryImpl.fromEnvironment(envPersistenceVolatile ?? null, environ, persistencePlugins);
|
|
50
50
|
const envDataManager = new EnvDataManager_1.EnvDataManager(envPersistenceFactory);
|
|
51
51
|
const flowsManager = new DonobuFlowsManager_1.DonobuFlowsManager(donobuDeploymentEnvironment, gptClientFactory, gptConfigsManager, agentsManager, flowsPersistenceRegistry, envDataManager, controlPanelFactory, environ, resolvedToolRegistry, targetRuntimePlugins);
|
|
52
|
-
const testsPersistenceRegistry = await TestsPersistenceRegistry_1.TestsPersistenceRegistryImpl.fromEnvironment(environ);
|
|
52
|
+
const testsPersistenceRegistry = await TestsPersistenceRegistry_1.TestsPersistenceRegistryImpl.fromEnvironment(environ, flowsPersistenceRegistry);
|
|
53
53
|
const testsManager = new TestsManager_1.TestsManager(testsPersistenceRegistry, flowsManager);
|
|
54
54
|
const suitesPersistenceRegistry = await SuitesPersistenceRegistry_1.SuitesPersistenceRegistryImpl.fromEnvironment(environ);
|
|
55
55
|
const suitesManager = new SuitesManager_1.SuitesManager(suitesPersistenceRegistry, testsPersistenceRegistry);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CreateDonobuFlow } from '../models/CreateDonobuFlow';
|
|
2
2
|
import type { CreateTest } from '../models/CreateTest';
|
|
3
3
|
import type { PaginatedResult } from '../models/PaginatedResult';
|
|
4
|
-
import type { TestMetadata, TestsQuery } from '../models/TestMetadata';
|
|
4
|
+
import type { TestListItem, TestMetadata, TestsQuery } from '../models/TestMetadata';
|
|
5
5
|
import type { TestsPersistenceRegistry } from '../persistence/tests/TestsPersistenceRegistry';
|
|
6
6
|
import type { DonobuFlowsManager } from './DonobuFlowsManager';
|
|
7
7
|
export declare class TestsManager {
|
|
@@ -10,7 +10,7 @@ export declare class TestsManager {
|
|
|
10
10
|
constructor(testsPersistenceRegistry: TestsPersistenceRegistry, flowsManager: DonobuFlowsManager);
|
|
11
11
|
createTest(params: CreateTest): Promise<TestMetadata>;
|
|
12
12
|
getTestById(testId: string): Promise<TestMetadata>;
|
|
13
|
-
getTests(query: TestsQuery): Promise<PaginatedResult<
|
|
13
|
+
getTests(query: TestsQuery): Promise<PaginatedResult<TestListItem>>;
|
|
14
14
|
/**
|
|
15
15
|
* Update a test in the persistence layer where it exists.
|
|
16
16
|
* Iterates layers, updates in the first one that has it, ignores
|
|
@@ -63,6 +63,13 @@ class TestsManager {
|
|
|
63
63
|
// TestMetadata has no `created_at` field — fall back to "now" so newly
|
|
64
64
|
// returned items sort to the most-recent end. For other sort columns,
|
|
65
65
|
// map the snake_case API name to the camelCase JS field.
|
|
66
|
+
//
|
|
67
|
+
// `flow_count` and `latest_flow_created_at` are only known at the
|
|
68
|
+
// persistence layer (SQLite computes them via JOIN; the cloud handles
|
|
69
|
+
// them server-side). Returning 0 here means each layer's pre-sorted
|
|
70
|
+
// results stay in their own order during the federated merge, but
|
|
71
|
+
// ordering across layers degrades to "first seen wins" — fine for the
|
|
72
|
+
// common single-primary-layer case.
|
|
66
73
|
const fieldFor = (test) => {
|
|
67
74
|
switch (sortBy) {
|
|
68
75
|
case 'created_at':
|
|
@@ -73,6 +80,9 @@ class TestsManager {
|
|
|
73
80
|
return test.suiteId ?? '';
|
|
74
81
|
case 'next_run_mode':
|
|
75
82
|
return test.nextRunMode;
|
|
83
|
+
case 'flow_count':
|
|
84
|
+
case 'latest_flow_created_at':
|
|
85
|
+
return 0;
|
|
76
86
|
default:
|
|
77
87
|
return '';
|
|
78
88
|
}
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
/**
|
|
3
|
+
* Create a schema to validate a paginated result.
|
|
4
|
+
*
|
|
5
|
+
* @param itemSchema - The Zod schema for the items in the result.
|
|
6
|
+
* @returns The Zod schema for the paginated result.
|
|
7
|
+
*/
|
|
8
|
+
export declare const createPaginatedResultSchema: <T extends z.ZodTypeAny>(itemSchema: T) => z.ZodObject<{
|
|
9
|
+
items: z.ZodArray<T>;
|
|
10
|
+
nextPageToken: z.ZodOptional<z.ZodString>;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
export declare const PaginatedResultSchema: z.ZodObject<{
|
|
13
|
+
items: z.ZodArray<z.ZodUnknown>;
|
|
14
|
+
nextPageToken: z.ZodOptional<z.ZodString>;
|
|
15
|
+
}, z.core.$strip>;
|
|
1
16
|
/**
|
|
2
17
|
* Result of a paginated query.
|
|
3
18
|
*/
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PaginatedResultSchema = exports.createPaginatedResultSchema = void 0;
|
|
4
|
+
const v4_1 = require("zod/v4");
|
|
2
5
|
/**
|
|
3
|
-
*
|
|
6
|
+
* Create a schema to validate a paginated result.
|
|
7
|
+
*
|
|
8
|
+
* @param itemSchema - The Zod schema for the items in the result.
|
|
9
|
+
* @returns The Zod schema for the paginated result.
|
|
4
10
|
*/
|
|
5
|
-
|
|
11
|
+
const createPaginatedResultSchema = (itemSchema) => v4_1.z.object({
|
|
12
|
+
items: v4_1.z.array(itemSchema),
|
|
13
|
+
nextPageToken: v4_1.z.string().optional(),
|
|
14
|
+
});
|
|
15
|
+
exports.createPaginatedResultSchema = createPaginatedResultSchema;
|
|
16
|
+
exports.PaginatedResultSchema = (0, exports.createPaginatedResultSchema)(v4_1.z.unknown());
|
|
6
17
|
//# sourceMappingURL=PaginatedResult.js.map
|