donobu 5.52.2 → 5.53.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/page/extendPage.js +3 -1
- package/dist/esm/lib/test/testExtension.js +64 -9
- package/dist/esm/managers/DonobuFlowsManager.js +3 -1
- package/dist/esm/managers/PageInspector.js +43 -15
- package/dist/esm/managers/TestsManager.js +3 -1
- package/dist/esm/models/CreateSuite.d.ts +1 -0
- package/dist/esm/models/CreateTest.d.ts +1 -0
- package/dist/esm/models/FlowMetadata.d.ts +1 -0
- package/dist/esm/models/MetadataVersion.d.ts +14 -0
- package/dist/esm/models/MetadataVersion.js +17 -0
- package/dist/esm/models/RunConfig.d.ts +1 -0
- package/dist/esm/models/RunConfig.js +8 -0
- package/dist/esm/models/SuiteMetadata.d.ts +1 -0
- package/dist/esm/models/TestMetadata.d.ts +5 -0
- package/dist/esm/persistence/normalizeFlowMetadata.d.ts +0 -5
- package/dist/esm/persistence/normalizeFlowMetadata.js +2 -7
- package/dist/lib/page/extendPage.js +3 -1
- package/dist/lib/test/testExtension.js +64 -9
- package/dist/managers/DonobuFlowsManager.js +3 -1
- package/dist/managers/PageInspector.js +43 -15
- package/dist/managers/TestsManager.js +3 -1
- package/dist/models/CreateSuite.d.ts +1 -0
- package/dist/models/CreateTest.d.ts +1 -0
- package/dist/models/FlowMetadata.d.ts +1 -0
- package/dist/models/MetadataVersion.d.ts +14 -0
- package/dist/models/MetadataVersion.js +17 -0
- package/dist/models/RunConfig.d.ts +1 -0
- package/dist/models/RunConfig.js +8 -0
- package/dist/models/SuiteMetadata.d.ts +1 -0
- package/dist/models/TestMetadata.d.ts +5 -0
- package/dist/persistence/normalizeFlowMetadata.d.ts +0 -5
- package/dist/persistence/normalizeFlowMetadata.js +2 -7
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@ const InteractionVisualizer_1 = require("../../managers/InteractionVisualizer");
|
|
|
15
15
|
const ToolManager_1 = require("../../managers/ToolManager");
|
|
16
16
|
const WebTargetInspector_1 = require("../../managers/WebTargetInspector");
|
|
17
17
|
const ControlPanel_1 = require("../../models/ControlPanel");
|
|
18
|
+
const MetadataVersion_1 = require("../../models/MetadataVersion");
|
|
18
19
|
const AnalyzePageTextTool_1 = require("../../tools/AnalyzePageTextTool");
|
|
19
20
|
const AssertTool_1 = require("../../tools/AssertTool");
|
|
20
21
|
const AuditTool_1 = require("../../tools/AuditTool");
|
|
@@ -88,7 +89,7 @@ async function extendPage(page, options) {
|
|
|
88
89
|
const sharedState = {
|
|
89
90
|
donobuFlowMetadata: {
|
|
90
91
|
id: options?.flowId ?? (0, crypto_1.randomUUID)(),
|
|
91
|
-
metadataVersion:
|
|
92
|
+
metadataVersion: MetadataVersion_1.CURRENT_METADATA_VERSION,
|
|
92
93
|
name: null,
|
|
93
94
|
createdWithDonobuVersion: MiscUtils_1.MiscUtils.DONOBU_VERSION,
|
|
94
95
|
target: 'web',
|
|
@@ -104,6 +105,7 @@ async function extendPage(page, options) {
|
|
|
104
105
|
targetWebsite: PLACEHOLDER_FLOW_URL,
|
|
105
106
|
},
|
|
106
107
|
envVars: options?.envVars ?? null,
|
|
108
|
+
tags: [],
|
|
107
109
|
gptConfigName: null,
|
|
108
110
|
hasGptConfigNameOverride: false,
|
|
109
111
|
customTools: null,
|
|
@@ -409,6 +409,7 @@ exports.test = test_1.test.extend({
|
|
|
409
409
|
extendedPage._dnb.donobuFlowMetadata.name = getSanitizedTestName(testInfo);
|
|
410
410
|
extendedPage._dnb.donobuFlowMetadata.testId = testInfo.testId;
|
|
411
411
|
extendedPage._dnb.donobuFlowMetadata.overallObjective = overallObjective;
|
|
412
|
+
extendedPage._dnb.donobuFlowMetadata.tags = testInfo.tags;
|
|
412
413
|
// Register browser console and network listeners so that logs from these
|
|
413
414
|
// sources are captured into the flow's logBuffer. In Studio-launched flows
|
|
414
415
|
// this is done by WebTargetInspector.initialize(), but that method is not
|
|
@@ -421,12 +422,27 @@ exports.test = test_1.test.extend({
|
|
|
421
422
|
const asyncScope = new async_hooks_1.AsyncResource('DonobuTestContext');
|
|
422
423
|
const boundUse = asyncScope.bind(use);
|
|
423
424
|
try {
|
|
425
|
+
const testMetadata = flowMetadataToTestMetadata(testInfo.testId, extendedPage._dnb.donobuFlowMetadata);
|
|
424
426
|
try {
|
|
425
|
-
const testMetadata = flowMetadataToTestMetadata(testInfo.testId, extendedPage._dnb.donobuFlowMetadata);
|
|
426
427
|
await extendedPage._dnb.testsPersistence.createTest(testMetadata);
|
|
427
428
|
}
|
|
428
429
|
catch {
|
|
429
|
-
// Test already exists from a prior run
|
|
430
|
+
// Test already exists from a prior run. Refresh the fields the SDK
|
|
431
|
+
// is the source of truth for so changes in the test code (e.g. a
|
|
432
|
+
// new `@tag`, an updated overallObjective annotation, additional
|
|
433
|
+
// `ENV` annotations, a different target URL) flow through to the
|
|
434
|
+
// persisted record — while leaving Studio-owned fields like
|
|
435
|
+
// `suiteId` and `nextRunMode` untouched.
|
|
436
|
+
const existing = await extendedPage._dnb.testsPersistence.getTestById(testInfo.testId);
|
|
437
|
+
await extendedPage._dnb.testsPersistence.updateTest({
|
|
438
|
+
...existing,
|
|
439
|
+
provenance: testMetadata.provenance,
|
|
440
|
+
web: testMetadata.web,
|
|
441
|
+
envVars: testMetadata.envVars,
|
|
442
|
+
overallObjective: testMetadata.overallObjective,
|
|
443
|
+
allowedTools: testMetadata.allowedTools,
|
|
444
|
+
tags: testMetadata.tags,
|
|
445
|
+
});
|
|
430
446
|
}
|
|
431
447
|
await extendedPage._dnb.persistence.setFlowMetadata(extendedPage._dnb.donobuFlowMetadata);
|
|
432
448
|
await boundUse(extendedPage);
|
|
@@ -846,6 +862,43 @@ async function attachStepScreenshots(sharedState, testInfo) {
|
|
|
846
862
|
contentType: 'application/json',
|
|
847
863
|
});
|
|
848
864
|
}
|
|
865
|
+
/**
|
|
866
|
+
* Build the per-flow `donobu-test-result.json` payload the website renders as
|
|
867
|
+
* the report's summary row + error/skip sections: the test-level scalars
|
|
868
|
+
* (status, objective, annotations, assertion errors).
|
|
869
|
+
*/
|
|
870
|
+
function buildDonobuTestResult(testInfo) {
|
|
871
|
+
const objective = testInfo.annotations.find((a) => a.type === 'objective')?.description ??
|
|
872
|
+
null;
|
|
873
|
+
return {
|
|
874
|
+
status: testInfo.status ?? 'unknown',
|
|
875
|
+
specTitle: testInfo.title,
|
|
876
|
+
file: path_1.default.relative(testInfo.config.rootDir, testInfo.file),
|
|
877
|
+
projectName: testInfo.project.name,
|
|
878
|
+
durationMs: testInfo.duration,
|
|
879
|
+
objective,
|
|
880
|
+
annotations: testInfo.annotations,
|
|
881
|
+
// TestInfoError exposes only message/stack at runtime; the assertion diff
|
|
882
|
+
// (expected/received) is embedded in the message for expect() failures, and
|
|
883
|
+
// the stack carries the code frame — enough for the website's error block.
|
|
884
|
+
errors: (testInfo.errors ?? []).map((e) => ({
|
|
885
|
+
message: e.message,
|
|
886
|
+
stack: e.stack,
|
|
887
|
+
})),
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Upload a per-flow JSON artifact through the flow's persistence layer.
|
|
892
|
+
* Best-effort: a failed upload must never affect the test result.
|
|
893
|
+
*/
|
|
894
|
+
async function persistFlowJson(persistence, flowId, fileId, value) {
|
|
895
|
+
try {
|
|
896
|
+
await persistence.setFlowFile(flowId, fileId, Buffer.from(JSON.stringify(value), 'utf8'));
|
|
897
|
+
}
|
|
898
|
+
catch (error) {
|
|
899
|
+
Logger_1.appLogger.warn(`Failed to persist report artifact "${fileId}" for flow ${flowId}: ${error.message}`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
849
902
|
/**
|
|
850
903
|
* Capture a live screenshot of the flow's final visual state at teardown (page
|
|
851
904
|
* still open) and persist it as a per-flow file — the single source of truth
|
|
@@ -902,16 +955,15 @@ async function finalizeTest(page, testInfo, logBuffer, videoOption) {
|
|
|
902
955
|
body: JSON.stringify(sharedState.donobuFlowMetadata, null, 2),
|
|
903
956
|
contentType: 'application/json',
|
|
904
957
|
});
|
|
958
|
+
// Persist the per-flow test result (status, objective, annotations, and
|
|
959
|
+
// assertion errors with snippets/diffs) so the website can render the
|
|
960
|
+
// report's summary row + error section. Playwright's TestInfoError already
|
|
961
|
+
// carries the code `snippet`, so no source-file access is needed.
|
|
962
|
+
await persistFlowJson(sharedState.persistence, sharedState.donobuFlowMetadata.id, 'donobu-test-result.json', buildDonobuTestResult(testInfo));
|
|
905
963
|
// Persist captured flow logs so they are available in the Donobu UI,
|
|
906
964
|
// mirroring what DonobuFlowsManager does for Studio-launched flows.
|
|
907
965
|
if (logBuffer) {
|
|
908
|
-
|
|
909
|
-
const snapshot = logBuffer.snapshot();
|
|
910
|
-
await sharedState.persistence.setFlowFile(sharedState.donobuFlowMetadata.id, 'logs.json', Buffer.from(JSON.stringify(snapshot)));
|
|
911
|
-
}
|
|
912
|
-
catch (error) {
|
|
913
|
-
Logger_1.appLogger.error('Failed to persist flow logs:', error);
|
|
914
|
-
}
|
|
966
|
+
await persistFlowJson(sharedState.persistence, sharedState.donobuFlowMetadata.id, 'logs.json', logBuffer.snapshot());
|
|
915
967
|
}
|
|
916
968
|
// Attach step-level screenshots from the flow's tool call history.
|
|
917
969
|
// These enable the HTML report to show a visual timeline of what the
|
|
@@ -927,6 +979,7 @@ async function finalizeTest(page, testInfo, logBuffer, videoOption) {
|
|
|
927
979
|
body: JSON.stringify(nativeSteps),
|
|
928
980
|
contentType: 'application/json',
|
|
929
981
|
});
|
|
982
|
+
await persistFlowJson(sharedState.persistence, sharedState.donobuFlowMetadata.id, 'donobu-native-steps.json', nativeSteps);
|
|
930
983
|
}
|
|
931
984
|
}
|
|
932
985
|
catch {
|
|
@@ -945,6 +998,7 @@ async function finalizeTest(page, testInfo, logBuffer, videoOption) {
|
|
|
945
998
|
body: JSON.stringify(aiInvocations),
|
|
946
999
|
contentType: 'application/json',
|
|
947
1000
|
});
|
|
1001
|
+
await persistFlowJson(sharedState.persistence, sharedState.donobuFlowMetadata.id, 'donobu-ai-invocations.json', aiInvocations);
|
|
948
1002
|
}
|
|
949
1003
|
}
|
|
950
1004
|
catch {
|
|
@@ -1062,6 +1116,7 @@ function flowMetadataToTestMetadata(testId, flowMeta) {
|
|
|
1062
1116
|
resultJsonSchema: flowMeta.resultJsonSchema,
|
|
1063
1117
|
callbackUrl: flowMeta.callbackUrl,
|
|
1064
1118
|
maxToolCalls: flowMeta.maxToolCalls,
|
|
1119
|
+
tags: flowMeta.tags,
|
|
1065
1120
|
suiteId: null,
|
|
1066
1121
|
nextRunMode: 'DETERMINISTIC',
|
|
1067
1122
|
provenance: (0, buildProvenance_1.buildProvenance)('CODE'),
|
|
@@ -53,6 +53,7 @@ const ToolRequiresGptException_1 = require("../exceptions/ToolRequiresGptExcepti
|
|
|
53
53
|
const UnknownToolException_1 = require("../exceptions/UnknownToolException");
|
|
54
54
|
const CreateDonobuFlow_1 = require("../models/CreateDonobuFlow");
|
|
55
55
|
const GptConfig_1 = require("../models/GptConfig");
|
|
56
|
+
const MetadataVersion_1 = require("../models/MetadataVersion");
|
|
56
57
|
const resolveTargetRuntime_1 = require("../targets/resolveTargetRuntime");
|
|
57
58
|
const CustomToolRunnerTool_1 = require("../tools/CustomToolRunnerTool");
|
|
58
59
|
const buildProvenance_1 = require("../utils/buildProvenance");
|
|
@@ -162,7 +163,7 @@ class DonobuFlowsManager {
|
|
|
162
163
|
const flowMetadata = {
|
|
163
164
|
id: flowId,
|
|
164
165
|
target: flowParams.target,
|
|
165
|
-
metadataVersion:
|
|
166
|
+
metadataVersion: MetadataVersion_1.CURRENT_METADATA_VERSION,
|
|
166
167
|
createdWithDonobuVersion: MiscUtils_1.MiscUtils.DONOBU_VERSION,
|
|
167
168
|
name: flowParams.name || null,
|
|
168
169
|
envVars: allowedEnvVarsByName,
|
|
@@ -185,6 +186,7 @@ class DonobuFlowsManager {
|
|
|
185
186
|
state: 'UNSTARTED',
|
|
186
187
|
nextState: null,
|
|
187
188
|
videoDisabled: flowParams.videoDisabled,
|
|
189
|
+
tags: [],
|
|
188
190
|
testId: flowParams.testId ?? null,
|
|
189
191
|
// Target-specific fields (browser, targetWebsite, isControlPanelEnabled, etc.)
|
|
190
192
|
...targetRuntime.getMetadataFields(),
|
|
@@ -933,24 +933,22 @@ class PageInspector {
|
|
|
933
933
|
if (document.scrollingElement) {
|
|
934
934
|
maybeAddScrollable(document.scrollingElement);
|
|
935
935
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
// Skip if this element already carries a value (e.g. assigned via <label>)
|
|
946
|
-
return;
|
|
936
|
+
/**
|
|
937
|
+
* Run the visibility / enabled / top-most checks on a single element and,
|
|
938
|
+
* if they pass, assign it the next interactable number. Returns `true` if
|
|
939
|
+
* the element (or, via the <label htmlFor> mapping, its associated control)
|
|
940
|
+
* was attributed.
|
|
941
|
+
*/
|
|
942
|
+
function tryAttributeElement(element) {
|
|
943
|
+
if (element.hasAttribute(interactableAttribute)) {
|
|
944
|
+
return false;
|
|
947
945
|
}
|
|
948
946
|
const rect = element.getBoundingClientRect();
|
|
949
947
|
const style = window.getComputedStyle(element);
|
|
950
948
|
const visible = isElementVisible(rect, style) && isElementMoreThanHalfInViewport(rect);
|
|
951
949
|
const enabled = isElementEnabled(element, style);
|
|
952
950
|
if (!visible || !enabled) {
|
|
953
|
-
return;
|
|
951
|
+
return false;
|
|
954
952
|
}
|
|
955
953
|
// Check a few probe points to make sure the element is top-most
|
|
956
954
|
for (const pt of getPointsToCheck(rect)) {
|
|
@@ -959,9 +957,9 @@ class PageInspector {
|
|
|
959
957
|
if (elToCheck === element) {
|
|
960
958
|
element.setAttribute(interactableAttribute, offset.toString());
|
|
961
959
|
offset++;
|
|
962
|
-
return; // this element done
|
|
960
|
+
return true; // this element done
|
|
963
961
|
}
|
|
964
|
-
// Handle <label> -> control mapping
|
|
962
|
+
// Handle <label> -> control mapping (explicit `for`/`htmlFor`)
|
|
965
963
|
if (elToCheck.tagName.toLowerCase() === 'label' &&
|
|
966
964
|
elToCheck.htmlFor) {
|
|
967
965
|
const forId = elToCheck.htmlFor;
|
|
@@ -972,11 +970,41 @@ class PageInspector {
|
|
|
972
970
|
control.setAttribute(interactableAttribute, offset.toString());
|
|
973
971
|
offset++;
|
|
974
972
|
}
|
|
975
|
-
return;
|
|
973
|
+
return true;
|
|
976
974
|
}
|
|
977
975
|
elToCheck = elToCheck.parentElement;
|
|
978
976
|
}
|
|
979
977
|
}
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
// 2) Iterate and assign numbers
|
|
981
|
+
uniqueElements.forEach((element) => {
|
|
982
|
+
if (element === document.scrollingElement) {
|
|
983
|
+
// Special-case: always keep the root scrolling element
|
|
984
|
+
element.setAttribute(interactableAttribute, offset.toString());
|
|
985
|
+
offset++;
|
|
986
|
+
return; // skip the usual checks
|
|
987
|
+
}
|
|
988
|
+
else if (element.hasAttribute(interactableAttribute)) {
|
|
989
|
+
// Skip if this element already carries a value (e.g. assigned via <label>)
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
if (tryAttributeElement(element)) {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
// Fallback: the element is a visually-hidden native control (e.g. a 0x0,
|
|
996
|
+
// opacity-0, pointer-events:none <input>) wrapped in a styled <label>.
|
|
997
|
+
// This is the standard pattern for Ant Design Segmented/Radio/Checkbox/
|
|
998
|
+
// Switch and many other component libraries: the native input is hidden
|
|
999
|
+
// and the surrounding <label> is the real clickable surface. Since the
|
|
1000
|
+
// hidden control fails the visibility/enabled checks above, attribute the
|
|
1001
|
+
// wrapping <label> instead so the toggle is still annotated.
|
|
1002
|
+
const wrappingLabel = element.closest('label');
|
|
1003
|
+
if (wrappingLabel &&
|
|
1004
|
+
wrappingLabel !== element &&
|
|
1005
|
+
!wrappingLabel.hasAttribute(interactableAttribute)) {
|
|
1006
|
+
tryAttributeElement(wrappingLabel);
|
|
1007
|
+
}
|
|
980
1008
|
});
|
|
981
1009
|
return offset;
|
|
982
1010
|
}
|
|
@@ -5,6 +5,7 @@ const crypto_1 = require("crypto");
|
|
|
5
5
|
const CannotDeleteRunningFlowException_1 = require("../exceptions/CannotDeleteRunningFlowException");
|
|
6
6
|
const SuiteNotFoundException_1 = require("../exceptions/SuiteNotFoundException");
|
|
7
7
|
const TestNotFoundException_1 = require("../exceptions/TestNotFoundException");
|
|
8
|
+
const MetadataVersion_1 = require("../models/MetadataVersion");
|
|
8
9
|
const buildProvenance_1 = require("../utils/buildProvenance");
|
|
9
10
|
const displayName_1 = require("../utils/displayName");
|
|
10
11
|
const FederatedPagination_1 = require("./FederatedPagination");
|
|
@@ -24,7 +25,7 @@ class TestsManager {
|
|
|
24
25
|
: undefined;
|
|
25
26
|
const testMetadata = {
|
|
26
27
|
id: testId,
|
|
27
|
-
metadataVersion:
|
|
28
|
+
metadataVersion: MetadataVersion_1.CURRENT_METADATA_VERSION,
|
|
28
29
|
name: (0, displayName_1.getDisplayName)({ name: params.name ?? null, web }),
|
|
29
30
|
suiteId: params.suiteId ?? null,
|
|
30
31
|
nextRunMode: params.nextRunMode ?? 'AUTONOMOUS',
|
|
@@ -38,6 +39,7 @@ class TestsManager {
|
|
|
38
39
|
resultJsonSchema: params.resultJsonSchema ?? null,
|
|
39
40
|
maxToolCalls: params.maxToolCalls ?? null,
|
|
40
41
|
videoDisabled: params.videoDisabled,
|
|
42
|
+
tags: [],
|
|
41
43
|
provenance: (0, buildProvenance_1.buildProvenance)('DONOBU_STUDIO'),
|
|
42
44
|
};
|
|
43
45
|
// If the test is part of a suite, write it to the same persistence layer
|
|
@@ -24,6 +24,7 @@ export declare const CreateSuiteSchema: z.ZodObject<{
|
|
|
24
24
|
callbackUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
25
25
|
maxToolCalls: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
26
26
|
videoDisabled: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodBoolean>>>;
|
|
27
|
+
tags: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
27
28
|
name: z.ZodString;
|
|
28
29
|
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
29
30
|
web: z.ZodOptional<z.ZodObject<{
|
|
@@ -23,6 +23,7 @@ export declare const CreateTestSchema: z.ZodObject<{
|
|
|
23
23
|
callbackUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
24
24
|
maxToolCalls: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
25
25
|
videoDisabled: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodBoolean>>>;
|
|
26
|
+
tags: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
26
27
|
target: z.ZodString;
|
|
27
28
|
web: z.ZodOptional<z.ZodObject<{
|
|
28
29
|
browser: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
@@ -140,6 +140,7 @@ export declare const FlowMetadataSchema: z.ZodObject<{
|
|
|
140
140
|
callbackUrl: z.ZodNullable<z.ZodString>;
|
|
141
141
|
maxToolCalls: z.ZodNullable<z.ZodNumber>;
|
|
142
142
|
videoDisabled: z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
143
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
143
144
|
id: z.ZodString;
|
|
144
145
|
metadataVersion: z.ZodOptional<z.ZodNumber>;
|
|
145
146
|
name: z.ZodNullable<z.ZodString>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Current metadata schema version. Bump this when the metadata JSON structure
|
|
3
|
+
* changes in a way that requires read-time normalization.
|
|
4
|
+
*
|
|
5
|
+
* Shared by Flow and Test metadata — both schemas carry the field as a single
|
|
6
|
+
* monotonic counter so a single bump covers a schema change that touches
|
|
7
|
+
* either or both.
|
|
8
|
+
*
|
|
9
|
+
* History:
|
|
10
|
+
* v1 — first version to carry `metadataVersion`. Replaced top-level
|
|
11
|
+
* `browser` / `targetWebsite` with the `{ target, web }` wrapper.
|
|
12
|
+
*/
|
|
13
|
+
export declare const CURRENT_METADATA_VERSION = 1;
|
|
14
|
+
//# sourceMappingURL=MetadataVersion.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CURRENT_METADATA_VERSION = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Current metadata schema version. Bump this when the metadata JSON structure
|
|
6
|
+
* changes in a way that requires read-time normalization.
|
|
7
|
+
*
|
|
8
|
+
* Shared by Flow and Test metadata — both schemas carry the field as a single
|
|
9
|
+
* monotonic counter so a single bump covers a schema change that touches
|
|
10
|
+
* either or both.
|
|
11
|
+
*
|
|
12
|
+
* History:
|
|
13
|
+
* v1 — first version to carry `metadataVersion`. Replaced top-level
|
|
14
|
+
* `browser` / `targetWebsite` with the `{ target, web }` wrapper.
|
|
15
|
+
*/
|
|
16
|
+
exports.CURRENT_METADATA_VERSION = 1;
|
|
17
|
+
//# sourceMappingURL=MetadataVersion.js.map
|
|
@@ -255,6 +255,7 @@ export declare const RunConfigSchema: z.ZodObject<{
|
|
|
255
255
|
callbackUrl: z.ZodNullable<z.ZodString>;
|
|
256
256
|
maxToolCalls: z.ZodNullable<z.ZodNumber>;
|
|
257
257
|
videoDisabled: z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
258
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
258
259
|
}, z.core.$loose>;
|
|
259
260
|
export type RunConfig = z.infer<typeof RunConfigSchema>;
|
|
260
261
|
export {};
|
|
@@ -64,5 +64,13 @@ exports.RunConfigSchema = v4_1.z.looseObject({
|
|
|
64
64
|
.optional()
|
|
65
65
|
.nullable()
|
|
66
66
|
.describe('If true, do not record a video of the execution.'),
|
|
67
|
+
tags: v4_1.z
|
|
68
|
+
.array(v4_1.z.string())
|
|
69
|
+
.optional()
|
|
70
|
+
.describe('Tags applied to this run (e.g. "@smoke", "@regression"). For Playwright ' +
|
|
71
|
+
"tests, populated from Playwright's testInfo.tags — which itself includes " +
|
|
72
|
+
'both tags parsed from the test name and tags from the `tag` test details object. ' +
|
|
73
|
+
'An empty array is meaningful: it signals "this run has no tags," which ' +
|
|
74
|
+
'should override any name-parsed tags that consumers might otherwise infer.'),
|
|
67
75
|
});
|
|
68
76
|
//# sourceMappingURL=RunConfig.js.map
|
|
@@ -136,6 +136,7 @@ export declare const SuiteMetadataSchema: z.ZodObject<{
|
|
|
136
136
|
callbackUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
137
137
|
maxToolCalls: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
138
138
|
videoDisabled: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodBoolean>>>;
|
|
139
|
+
tags: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
139
140
|
id: z.ZodString;
|
|
140
141
|
metadataVersion: z.ZodOptional<z.ZodNumber>;
|
|
141
142
|
name: z.ZodString;
|
|
@@ -133,6 +133,7 @@ export declare const TestMetadataSchema: z.ZodObject<{
|
|
|
133
133
|
callbackUrl: z.ZodNullable<z.ZodString>;
|
|
134
134
|
maxToolCalls: z.ZodNullable<z.ZodNumber>;
|
|
135
135
|
videoDisabled: z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
136
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
136
137
|
id: z.ZodString;
|
|
137
138
|
metadataVersion: z.ZodOptional<z.ZodNumber>;
|
|
138
139
|
name: z.ZodNullable<z.ZodString>;
|
|
@@ -315,6 +316,7 @@ export declare const TestListItemSchema: z.ZodObject<{
|
|
|
315
316
|
callbackUrl: z.ZodNullable<z.ZodString>;
|
|
316
317
|
maxToolCalls: z.ZodNullable<z.ZodNumber>;
|
|
317
318
|
videoDisabled: z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
319
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
318
320
|
id: z.ZodString;
|
|
319
321
|
metadataVersion: z.ZodOptional<z.ZodNumber>;
|
|
320
322
|
name: z.ZodNullable<z.ZodString>;
|
|
@@ -473,6 +475,7 @@ export declare const TestListItemSchema: z.ZodObject<{
|
|
|
473
475
|
callbackUrl: z.ZodNullable<z.ZodString>;
|
|
474
476
|
maxToolCalls: z.ZodNullable<z.ZodNumber>;
|
|
475
477
|
videoDisabled: z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
478
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
476
479
|
id: z.ZodString;
|
|
477
480
|
metadataVersion: z.ZodOptional<z.ZodNumber>;
|
|
478
481
|
name: z.ZodNullable<z.ZodString>;
|
|
@@ -665,6 +668,7 @@ export declare const TestListItemPaginatedResultSchema: z.ZodObject<{
|
|
|
665
668
|
callbackUrl: z.ZodNullable<z.ZodString>;
|
|
666
669
|
maxToolCalls: z.ZodNullable<z.ZodNumber>;
|
|
667
670
|
videoDisabled: z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
671
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
668
672
|
id: z.ZodString;
|
|
669
673
|
metadataVersion: z.ZodOptional<z.ZodNumber>;
|
|
670
674
|
name: z.ZodNullable<z.ZodString>;
|
|
@@ -823,6 +827,7 @@ export declare const TestListItemPaginatedResultSchema: z.ZodObject<{
|
|
|
823
827
|
callbackUrl: z.ZodNullable<z.ZodString>;
|
|
824
828
|
maxToolCalls: z.ZodNullable<z.ZodNumber>;
|
|
825
829
|
videoDisabled: z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
830
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
826
831
|
id: z.ZodString;
|
|
827
832
|
metadataVersion: z.ZodOptional<z.ZodNumber>;
|
|
828
833
|
name: z.ZodNullable<z.ZodString>;
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import type { FlowMetadata } from '../models/FlowMetadata';
|
|
2
|
-
/**
|
|
3
|
-
* Current metadata schema version. Bump this when the metadata JSON structure
|
|
4
|
-
* changes in a way that requires read-time normalization.
|
|
5
|
-
*/
|
|
6
|
-
export declare const CURRENT_METADATA_VERSION = 1;
|
|
7
2
|
/**
|
|
8
3
|
* Normalizes flow metadata written by older SDK versions to the current schema.
|
|
9
4
|
*
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CURRENT_METADATA_VERSION = void 0;
|
|
4
3
|
exports.normalizeFlowMetadata = normalizeFlowMetadata;
|
|
5
|
-
|
|
6
|
-
* Current metadata schema version. Bump this when the metadata JSON structure
|
|
7
|
-
* changes in a way that requires read-time normalization.
|
|
8
|
-
*/
|
|
9
|
-
exports.CURRENT_METADATA_VERSION = 1;
|
|
4
|
+
const MetadataVersion_1 = require("../models/MetadataVersion");
|
|
10
5
|
/**
|
|
11
6
|
* Normalizes flow metadata written by older SDK versions to the current schema.
|
|
12
7
|
*
|
|
@@ -16,7 +11,7 @@ exports.CURRENT_METADATA_VERSION = 1;
|
|
|
16
11
|
* introduced in migration v9).
|
|
17
12
|
*/
|
|
18
13
|
function normalizeFlowMetadata(raw) {
|
|
19
|
-
if (raw.metadataVersion ===
|
|
14
|
+
if (raw.metadataVersion === MetadataVersion_1.CURRENT_METADATA_VERSION) {
|
|
20
15
|
return raw;
|
|
21
16
|
}
|
|
22
17
|
// Pre-v5 format: `browser` and `targetWebsite` at the top level, no `target`.
|
|
@@ -15,6 +15,7 @@ const InteractionVisualizer_1 = require("../../managers/InteractionVisualizer");
|
|
|
15
15
|
const ToolManager_1 = require("../../managers/ToolManager");
|
|
16
16
|
const WebTargetInspector_1 = require("../../managers/WebTargetInspector");
|
|
17
17
|
const ControlPanel_1 = require("../../models/ControlPanel");
|
|
18
|
+
const MetadataVersion_1 = require("../../models/MetadataVersion");
|
|
18
19
|
const AnalyzePageTextTool_1 = require("../../tools/AnalyzePageTextTool");
|
|
19
20
|
const AssertTool_1 = require("../../tools/AssertTool");
|
|
20
21
|
const AuditTool_1 = require("../../tools/AuditTool");
|
|
@@ -88,7 +89,7 @@ async function extendPage(page, options) {
|
|
|
88
89
|
const sharedState = {
|
|
89
90
|
donobuFlowMetadata: {
|
|
90
91
|
id: options?.flowId ?? (0, crypto_1.randomUUID)(),
|
|
91
|
-
metadataVersion:
|
|
92
|
+
metadataVersion: MetadataVersion_1.CURRENT_METADATA_VERSION,
|
|
92
93
|
name: null,
|
|
93
94
|
createdWithDonobuVersion: MiscUtils_1.MiscUtils.DONOBU_VERSION,
|
|
94
95
|
target: 'web',
|
|
@@ -104,6 +105,7 @@ async function extendPage(page, options) {
|
|
|
104
105
|
targetWebsite: PLACEHOLDER_FLOW_URL,
|
|
105
106
|
},
|
|
106
107
|
envVars: options?.envVars ?? null,
|
|
108
|
+
tags: [],
|
|
107
109
|
gptConfigName: null,
|
|
108
110
|
hasGptConfigNameOverride: false,
|
|
109
111
|
customTools: null,
|
|
@@ -409,6 +409,7 @@ exports.test = test_1.test.extend({
|
|
|
409
409
|
extendedPage._dnb.donobuFlowMetadata.name = getSanitizedTestName(testInfo);
|
|
410
410
|
extendedPage._dnb.donobuFlowMetadata.testId = testInfo.testId;
|
|
411
411
|
extendedPage._dnb.donobuFlowMetadata.overallObjective = overallObjective;
|
|
412
|
+
extendedPage._dnb.donobuFlowMetadata.tags = testInfo.tags;
|
|
412
413
|
// Register browser console and network listeners so that logs from these
|
|
413
414
|
// sources are captured into the flow's logBuffer. In Studio-launched flows
|
|
414
415
|
// this is done by WebTargetInspector.initialize(), but that method is not
|
|
@@ -421,12 +422,27 @@ exports.test = test_1.test.extend({
|
|
|
421
422
|
const asyncScope = new async_hooks_1.AsyncResource('DonobuTestContext');
|
|
422
423
|
const boundUse = asyncScope.bind(use);
|
|
423
424
|
try {
|
|
425
|
+
const testMetadata = flowMetadataToTestMetadata(testInfo.testId, extendedPage._dnb.donobuFlowMetadata);
|
|
424
426
|
try {
|
|
425
|
-
const testMetadata = flowMetadataToTestMetadata(testInfo.testId, extendedPage._dnb.donobuFlowMetadata);
|
|
426
427
|
await extendedPage._dnb.testsPersistence.createTest(testMetadata);
|
|
427
428
|
}
|
|
428
429
|
catch {
|
|
429
|
-
// Test already exists from a prior run
|
|
430
|
+
// Test already exists from a prior run. Refresh the fields the SDK
|
|
431
|
+
// is the source of truth for so changes in the test code (e.g. a
|
|
432
|
+
// new `@tag`, an updated overallObjective annotation, additional
|
|
433
|
+
// `ENV` annotations, a different target URL) flow through to the
|
|
434
|
+
// persisted record — while leaving Studio-owned fields like
|
|
435
|
+
// `suiteId` and `nextRunMode` untouched.
|
|
436
|
+
const existing = await extendedPage._dnb.testsPersistence.getTestById(testInfo.testId);
|
|
437
|
+
await extendedPage._dnb.testsPersistence.updateTest({
|
|
438
|
+
...existing,
|
|
439
|
+
provenance: testMetadata.provenance,
|
|
440
|
+
web: testMetadata.web,
|
|
441
|
+
envVars: testMetadata.envVars,
|
|
442
|
+
overallObjective: testMetadata.overallObjective,
|
|
443
|
+
allowedTools: testMetadata.allowedTools,
|
|
444
|
+
tags: testMetadata.tags,
|
|
445
|
+
});
|
|
430
446
|
}
|
|
431
447
|
await extendedPage._dnb.persistence.setFlowMetadata(extendedPage._dnb.donobuFlowMetadata);
|
|
432
448
|
await boundUse(extendedPage);
|
|
@@ -846,6 +862,43 @@ async function attachStepScreenshots(sharedState, testInfo) {
|
|
|
846
862
|
contentType: 'application/json',
|
|
847
863
|
});
|
|
848
864
|
}
|
|
865
|
+
/**
|
|
866
|
+
* Build the per-flow `donobu-test-result.json` payload the website renders as
|
|
867
|
+
* the report's summary row + error/skip sections: the test-level scalars
|
|
868
|
+
* (status, objective, annotations, assertion errors).
|
|
869
|
+
*/
|
|
870
|
+
function buildDonobuTestResult(testInfo) {
|
|
871
|
+
const objective = testInfo.annotations.find((a) => a.type === 'objective')?.description ??
|
|
872
|
+
null;
|
|
873
|
+
return {
|
|
874
|
+
status: testInfo.status ?? 'unknown',
|
|
875
|
+
specTitle: testInfo.title,
|
|
876
|
+
file: path_1.default.relative(testInfo.config.rootDir, testInfo.file),
|
|
877
|
+
projectName: testInfo.project.name,
|
|
878
|
+
durationMs: testInfo.duration,
|
|
879
|
+
objective,
|
|
880
|
+
annotations: testInfo.annotations,
|
|
881
|
+
// TestInfoError exposes only message/stack at runtime; the assertion diff
|
|
882
|
+
// (expected/received) is embedded in the message for expect() failures, and
|
|
883
|
+
// the stack carries the code frame — enough for the website's error block.
|
|
884
|
+
errors: (testInfo.errors ?? []).map((e) => ({
|
|
885
|
+
message: e.message,
|
|
886
|
+
stack: e.stack,
|
|
887
|
+
})),
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Upload a per-flow JSON artifact through the flow's persistence layer.
|
|
892
|
+
* Best-effort: a failed upload must never affect the test result.
|
|
893
|
+
*/
|
|
894
|
+
async function persistFlowJson(persistence, flowId, fileId, value) {
|
|
895
|
+
try {
|
|
896
|
+
await persistence.setFlowFile(flowId, fileId, Buffer.from(JSON.stringify(value), 'utf8'));
|
|
897
|
+
}
|
|
898
|
+
catch (error) {
|
|
899
|
+
Logger_1.appLogger.warn(`Failed to persist report artifact "${fileId}" for flow ${flowId}: ${error.message}`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
849
902
|
/**
|
|
850
903
|
* Capture a live screenshot of the flow's final visual state at teardown (page
|
|
851
904
|
* still open) and persist it as a per-flow file — the single source of truth
|
|
@@ -902,16 +955,15 @@ async function finalizeTest(page, testInfo, logBuffer, videoOption) {
|
|
|
902
955
|
body: JSON.stringify(sharedState.donobuFlowMetadata, null, 2),
|
|
903
956
|
contentType: 'application/json',
|
|
904
957
|
});
|
|
958
|
+
// Persist the per-flow test result (status, objective, annotations, and
|
|
959
|
+
// assertion errors with snippets/diffs) so the website can render the
|
|
960
|
+
// report's summary row + error section. Playwright's TestInfoError already
|
|
961
|
+
// carries the code `snippet`, so no source-file access is needed.
|
|
962
|
+
await persistFlowJson(sharedState.persistence, sharedState.donobuFlowMetadata.id, 'donobu-test-result.json', buildDonobuTestResult(testInfo));
|
|
905
963
|
// Persist captured flow logs so they are available in the Donobu UI,
|
|
906
964
|
// mirroring what DonobuFlowsManager does for Studio-launched flows.
|
|
907
965
|
if (logBuffer) {
|
|
908
|
-
|
|
909
|
-
const snapshot = logBuffer.snapshot();
|
|
910
|
-
await sharedState.persistence.setFlowFile(sharedState.donobuFlowMetadata.id, 'logs.json', Buffer.from(JSON.stringify(snapshot)));
|
|
911
|
-
}
|
|
912
|
-
catch (error) {
|
|
913
|
-
Logger_1.appLogger.error('Failed to persist flow logs:', error);
|
|
914
|
-
}
|
|
966
|
+
await persistFlowJson(sharedState.persistence, sharedState.donobuFlowMetadata.id, 'logs.json', logBuffer.snapshot());
|
|
915
967
|
}
|
|
916
968
|
// Attach step-level screenshots from the flow's tool call history.
|
|
917
969
|
// These enable the HTML report to show a visual timeline of what the
|
|
@@ -927,6 +979,7 @@ async function finalizeTest(page, testInfo, logBuffer, videoOption) {
|
|
|
927
979
|
body: JSON.stringify(nativeSteps),
|
|
928
980
|
contentType: 'application/json',
|
|
929
981
|
});
|
|
982
|
+
await persistFlowJson(sharedState.persistence, sharedState.donobuFlowMetadata.id, 'donobu-native-steps.json', nativeSteps);
|
|
930
983
|
}
|
|
931
984
|
}
|
|
932
985
|
catch {
|
|
@@ -945,6 +998,7 @@ async function finalizeTest(page, testInfo, logBuffer, videoOption) {
|
|
|
945
998
|
body: JSON.stringify(aiInvocations),
|
|
946
999
|
contentType: 'application/json',
|
|
947
1000
|
});
|
|
1001
|
+
await persistFlowJson(sharedState.persistence, sharedState.donobuFlowMetadata.id, 'donobu-ai-invocations.json', aiInvocations);
|
|
948
1002
|
}
|
|
949
1003
|
}
|
|
950
1004
|
catch {
|
|
@@ -1062,6 +1116,7 @@ function flowMetadataToTestMetadata(testId, flowMeta) {
|
|
|
1062
1116
|
resultJsonSchema: flowMeta.resultJsonSchema,
|
|
1063
1117
|
callbackUrl: flowMeta.callbackUrl,
|
|
1064
1118
|
maxToolCalls: flowMeta.maxToolCalls,
|
|
1119
|
+
tags: flowMeta.tags,
|
|
1065
1120
|
suiteId: null,
|
|
1066
1121
|
nextRunMode: 'DETERMINISTIC',
|
|
1067
1122
|
provenance: (0, buildProvenance_1.buildProvenance)('CODE'),
|
|
@@ -53,6 +53,7 @@ const ToolRequiresGptException_1 = require("../exceptions/ToolRequiresGptExcepti
|
|
|
53
53
|
const UnknownToolException_1 = require("../exceptions/UnknownToolException");
|
|
54
54
|
const CreateDonobuFlow_1 = require("../models/CreateDonobuFlow");
|
|
55
55
|
const GptConfig_1 = require("../models/GptConfig");
|
|
56
|
+
const MetadataVersion_1 = require("../models/MetadataVersion");
|
|
56
57
|
const resolveTargetRuntime_1 = require("../targets/resolveTargetRuntime");
|
|
57
58
|
const CustomToolRunnerTool_1 = require("../tools/CustomToolRunnerTool");
|
|
58
59
|
const buildProvenance_1 = require("../utils/buildProvenance");
|
|
@@ -162,7 +163,7 @@ class DonobuFlowsManager {
|
|
|
162
163
|
const flowMetadata = {
|
|
163
164
|
id: flowId,
|
|
164
165
|
target: flowParams.target,
|
|
165
|
-
metadataVersion:
|
|
166
|
+
metadataVersion: MetadataVersion_1.CURRENT_METADATA_VERSION,
|
|
166
167
|
createdWithDonobuVersion: MiscUtils_1.MiscUtils.DONOBU_VERSION,
|
|
167
168
|
name: flowParams.name || null,
|
|
168
169
|
envVars: allowedEnvVarsByName,
|
|
@@ -185,6 +186,7 @@ class DonobuFlowsManager {
|
|
|
185
186
|
state: 'UNSTARTED',
|
|
186
187
|
nextState: null,
|
|
187
188
|
videoDisabled: flowParams.videoDisabled,
|
|
189
|
+
tags: [],
|
|
188
190
|
testId: flowParams.testId ?? null,
|
|
189
191
|
// Target-specific fields (browser, targetWebsite, isControlPanelEnabled, etc.)
|
|
190
192
|
...targetRuntime.getMetadataFields(),
|
|
@@ -933,24 +933,22 @@ class PageInspector {
|
|
|
933
933
|
if (document.scrollingElement) {
|
|
934
934
|
maybeAddScrollable(document.scrollingElement);
|
|
935
935
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
// Skip if this element already carries a value (e.g. assigned via <label>)
|
|
946
|
-
return;
|
|
936
|
+
/**
|
|
937
|
+
* Run the visibility / enabled / top-most checks on a single element and,
|
|
938
|
+
* if they pass, assign it the next interactable number. Returns `true` if
|
|
939
|
+
* the element (or, via the <label htmlFor> mapping, its associated control)
|
|
940
|
+
* was attributed.
|
|
941
|
+
*/
|
|
942
|
+
function tryAttributeElement(element) {
|
|
943
|
+
if (element.hasAttribute(interactableAttribute)) {
|
|
944
|
+
return false;
|
|
947
945
|
}
|
|
948
946
|
const rect = element.getBoundingClientRect();
|
|
949
947
|
const style = window.getComputedStyle(element);
|
|
950
948
|
const visible = isElementVisible(rect, style) && isElementMoreThanHalfInViewport(rect);
|
|
951
949
|
const enabled = isElementEnabled(element, style);
|
|
952
950
|
if (!visible || !enabled) {
|
|
953
|
-
return;
|
|
951
|
+
return false;
|
|
954
952
|
}
|
|
955
953
|
// Check a few probe points to make sure the element is top-most
|
|
956
954
|
for (const pt of getPointsToCheck(rect)) {
|
|
@@ -959,9 +957,9 @@ class PageInspector {
|
|
|
959
957
|
if (elToCheck === element) {
|
|
960
958
|
element.setAttribute(interactableAttribute, offset.toString());
|
|
961
959
|
offset++;
|
|
962
|
-
return; // this element done
|
|
960
|
+
return true; // this element done
|
|
963
961
|
}
|
|
964
|
-
// Handle <label> -> control mapping
|
|
962
|
+
// Handle <label> -> control mapping (explicit `for`/`htmlFor`)
|
|
965
963
|
if (elToCheck.tagName.toLowerCase() === 'label' &&
|
|
966
964
|
elToCheck.htmlFor) {
|
|
967
965
|
const forId = elToCheck.htmlFor;
|
|
@@ -972,11 +970,41 @@ class PageInspector {
|
|
|
972
970
|
control.setAttribute(interactableAttribute, offset.toString());
|
|
973
971
|
offset++;
|
|
974
972
|
}
|
|
975
|
-
return;
|
|
973
|
+
return true;
|
|
976
974
|
}
|
|
977
975
|
elToCheck = elToCheck.parentElement;
|
|
978
976
|
}
|
|
979
977
|
}
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
// 2) Iterate and assign numbers
|
|
981
|
+
uniqueElements.forEach((element) => {
|
|
982
|
+
if (element === document.scrollingElement) {
|
|
983
|
+
// Special-case: always keep the root scrolling element
|
|
984
|
+
element.setAttribute(interactableAttribute, offset.toString());
|
|
985
|
+
offset++;
|
|
986
|
+
return; // skip the usual checks
|
|
987
|
+
}
|
|
988
|
+
else if (element.hasAttribute(interactableAttribute)) {
|
|
989
|
+
// Skip if this element already carries a value (e.g. assigned via <label>)
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
if (tryAttributeElement(element)) {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
// Fallback: the element is a visually-hidden native control (e.g. a 0x0,
|
|
996
|
+
// opacity-0, pointer-events:none <input>) wrapped in a styled <label>.
|
|
997
|
+
// This is the standard pattern for Ant Design Segmented/Radio/Checkbox/
|
|
998
|
+
// Switch and many other component libraries: the native input is hidden
|
|
999
|
+
// and the surrounding <label> is the real clickable surface. Since the
|
|
1000
|
+
// hidden control fails the visibility/enabled checks above, attribute the
|
|
1001
|
+
// wrapping <label> instead so the toggle is still annotated.
|
|
1002
|
+
const wrappingLabel = element.closest('label');
|
|
1003
|
+
if (wrappingLabel &&
|
|
1004
|
+
wrappingLabel !== element &&
|
|
1005
|
+
!wrappingLabel.hasAttribute(interactableAttribute)) {
|
|
1006
|
+
tryAttributeElement(wrappingLabel);
|
|
1007
|
+
}
|
|
980
1008
|
});
|
|
981
1009
|
return offset;
|
|
982
1010
|
}
|
|
@@ -5,6 +5,7 @@ const crypto_1 = require("crypto");
|
|
|
5
5
|
const CannotDeleteRunningFlowException_1 = require("../exceptions/CannotDeleteRunningFlowException");
|
|
6
6
|
const SuiteNotFoundException_1 = require("../exceptions/SuiteNotFoundException");
|
|
7
7
|
const TestNotFoundException_1 = require("../exceptions/TestNotFoundException");
|
|
8
|
+
const MetadataVersion_1 = require("../models/MetadataVersion");
|
|
8
9
|
const buildProvenance_1 = require("../utils/buildProvenance");
|
|
9
10
|
const displayName_1 = require("../utils/displayName");
|
|
10
11
|
const FederatedPagination_1 = require("./FederatedPagination");
|
|
@@ -24,7 +25,7 @@ class TestsManager {
|
|
|
24
25
|
: undefined;
|
|
25
26
|
const testMetadata = {
|
|
26
27
|
id: testId,
|
|
27
|
-
metadataVersion:
|
|
28
|
+
metadataVersion: MetadataVersion_1.CURRENT_METADATA_VERSION,
|
|
28
29
|
name: (0, displayName_1.getDisplayName)({ name: params.name ?? null, web }),
|
|
29
30
|
suiteId: params.suiteId ?? null,
|
|
30
31
|
nextRunMode: params.nextRunMode ?? 'AUTONOMOUS',
|
|
@@ -38,6 +39,7 @@ class TestsManager {
|
|
|
38
39
|
resultJsonSchema: params.resultJsonSchema ?? null,
|
|
39
40
|
maxToolCalls: params.maxToolCalls ?? null,
|
|
40
41
|
videoDisabled: params.videoDisabled,
|
|
42
|
+
tags: [],
|
|
41
43
|
provenance: (0, buildProvenance_1.buildProvenance)('DONOBU_STUDIO'),
|
|
42
44
|
};
|
|
43
45
|
// If the test is part of a suite, write it to the same persistence layer
|
|
@@ -24,6 +24,7 @@ export declare const CreateSuiteSchema: z.ZodObject<{
|
|
|
24
24
|
callbackUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
25
25
|
maxToolCalls: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
26
26
|
videoDisabled: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodBoolean>>>;
|
|
27
|
+
tags: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
27
28
|
name: z.ZodString;
|
|
28
29
|
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
29
30
|
web: z.ZodOptional<z.ZodObject<{
|
|
@@ -23,6 +23,7 @@ export declare const CreateTestSchema: z.ZodObject<{
|
|
|
23
23
|
callbackUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
24
24
|
maxToolCalls: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
25
25
|
videoDisabled: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodBoolean>>>;
|
|
26
|
+
tags: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
26
27
|
target: z.ZodString;
|
|
27
28
|
web: z.ZodOptional<z.ZodObject<{
|
|
28
29
|
browser: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
@@ -140,6 +140,7 @@ export declare const FlowMetadataSchema: z.ZodObject<{
|
|
|
140
140
|
callbackUrl: z.ZodNullable<z.ZodString>;
|
|
141
141
|
maxToolCalls: z.ZodNullable<z.ZodNumber>;
|
|
142
142
|
videoDisabled: z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
143
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
143
144
|
id: z.ZodString;
|
|
144
145
|
metadataVersion: z.ZodOptional<z.ZodNumber>;
|
|
145
146
|
name: z.ZodNullable<z.ZodString>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Current metadata schema version. Bump this when the metadata JSON structure
|
|
3
|
+
* changes in a way that requires read-time normalization.
|
|
4
|
+
*
|
|
5
|
+
* Shared by Flow and Test metadata — both schemas carry the field as a single
|
|
6
|
+
* monotonic counter so a single bump covers a schema change that touches
|
|
7
|
+
* either or both.
|
|
8
|
+
*
|
|
9
|
+
* History:
|
|
10
|
+
* v1 — first version to carry `metadataVersion`. Replaced top-level
|
|
11
|
+
* `browser` / `targetWebsite` with the `{ target, web }` wrapper.
|
|
12
|
+
*/
|
|
13
|
+
export declare const CURRENT_METADATA_VERSION = 1;
|
|
14
|
+
//# sourceMappingURL=MetadataVersion.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CURRENT_METADATA_VERSION = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Current metadata schema version. Bump this when the metadata JSON structure
|
|
6
|
+
* changes in a way that requires read-time normalization.
|
|
7
|
+
*
|
|
8
|
+
* Shared by Flow and Test metadata — both schemas carry the field as a single
|
|
9
|
+
* monotonic counter so a single bump covers a schema change that touches
|
|
10
|
+
* either or both.
|
|
11
|
+
*
|
|
12
|
+
* History:
|
|
13
|
+
* v1 — first version to carry `metadataVersion`. Replaced top-level
|
|
14
|
+
* `browser` / `targetWebsite` with the `{ target, web }` wrapper.
|
|
15
|
+
*/
|
|
16
|
+
exports.CURRENT_METADATA_VERSION = 1;
|
|
17
|
+
//# sourceMappingURL=MetadataVersion.js.map
|
|
@@ -255,6 +255,7 @@ export declare const RunConfigSchema: z.ZodObject<{
|
|
|
255
255
|
callbackUrl: z.ZodNullable<z.ZodString>;
|
|
256
256
|
maxToolCalls: z.ZodNullable<z.ZodNumber>;
|
|
257
257
|
videoDisabled: z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
258
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
258
259
|
}, z.core.$loose>;
|
|
259
260
|
export type RunConfig = z.infer<typeof RunConfigSchema>;
|
|
260
261
|
export {};
|
package/dist/models/RunConfig.js
CHANGED
|
@@ -64,5 +64,13 @@ exports.RunConfigSchema = v4_1.z.looseObject({
|
|
|
64
64
|
.optional()
|
|
65
65
|
.nullable()
|
|
66
66
|
.describe('If true, do not record a video of the execution.'),
|
|
67
|
+
tags: v4_1.z
|
|
68
|
+
.array(v4_1.z.string())
|
|
69
|
+
.optional()
|
|
70
|
+
.describe('Tags applied to this run (e.g. "@smoke", "@regression"). For Playwright ' +
|
|
71
|
+
"tests, populated from Playwright's testInfo.tags — which itself includes " +
|
|
72
|
+
'both tags parsed from the test name and tags from the `tag` test details object. ' +
|
|
73
|
+
'An empty array is meaningful: it signals "this run has no tags," which ' +
|
|
74
|
+
'should override any name-parsed tags that consumers might otherwise infer.'),
|
|
67
75
|
});
|
|
68
76
|
//# sourceMappingURL=RunConfig.js.map
|
|
@@ -136,6 +136,7 @@ export declare const SuiteMetadataSchema: z.ZodObject<{
|
|
|
136
136
|
callbackUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
137
137
|
maxToolCalls: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
138
138
|
videoDisabled: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodBoolean>>>;
|
|
139
|
+
tags: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString>>>;
|
|
139
140
|
id: z.ZodString;
|
|
140
141
|
metadataVersion: z.ZodOptional<z.ZodNumber>;
|
|
141
142
|
name: z.ZodString;
|
|
@@ -133,6 +133,7 @@ export declare const TestMetadataSchema: z.ZodObject<{
|
|
|
133
133
|
callbackUrl: z.ZodNullable<z.ZodString>;
|
|
134
134
|
maxToolCalls: z.ZodNullable<z.ZodNumber>;
|
|
135
135
|
videoDisabled: z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
136
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
136
137
|
id: z.ZodString;
|
|
137
138
|
metadataVersion: z.ZodOptional<z.ZodNumber>;
|
|
138
139
|
name: z.ZodNullable<z.ZodString>;
|
|
@@ -315,6 +316,7 @@ export declare const TestListItemSchema: z.ZodObject<{
|
|
|
315
316
|
callbackUrl: z.ZodNullable<z.ZodString>;
|
|
316
317
|
maxToolCalls: z.ZodNullable<z.ZodNumber>;
|
|
317
318
|
videoDisabled: z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
319
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
318
320
|
id: z.ZodString;
|
|
319
321
|
metadataVersion: z.ZodOptional<z.ZodNumber>;
|
|
320
322
|
name: z.ZodNullable<z.ZodString>;
|
|
@@ -473,6 +475,7 @@ export declare const TestListItemSchema: z.ZodObject<{
|
|
|
473
475
|
callbackUrl: z.ZodNullable<z.ZodString>;
|
|
474
476
|
maxToolCalls: z.ZodNullable<z.ZodNumber>;
|
|
475
477
|
videoDisabled: z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
478
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
476
479
|
id: z.ZodString;
|
|
477
480
|
metadataVersion: z.ZodOptional<z.ZodNumber>;
|
|
478
481
|
name: z.ZodNullable<z.ZodString>;
|
|
@@ -665,6 +668,7 @@ export declare const TestListItemPaginatedResultSchema: z.ZodObject<{
|
|
|
665
668
|
callbackUrl: z.ZodNullable<z.ZodString>;
|
|
666
669
|
maxToolCalls: z.ZodNullable<z.ZodNumber>;
|
|
667
670
|
videoDisabled: z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
671
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
668
672
|
id: z.ZodString;
|
|
669
673
|
metadataVersion: z.ZodOptional<z.ZodNumber>;
|
|
670
674
|
name: z.ZodNullable<z.ZodString>;
|
|
@@ -823,6 +827,7 @@ export declare const TestListItemPaginatedResultSchema: z.ZodObject<{
|
|
|
823
827
|
callbackUrl: z.ZodNullable<z.ZodString>;
|
|
824
828
|
maxToolCalls: z.ZodNullable<z.ZodNumber>;
|
|
825
829
|
videoDisabled: z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
830
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
826
831
|
id: z.ZodString;
|
|
827
832
|
metadataVersion: z.ZodOptional<z.ZodNumber>;
|
|
828
833
|
name: z.ZodNullable<z.ZodString>;
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import type { FlowMetadata } from '../models/FlowMetadata';
|
|
2
|
-
/**
|
|
3
|
-
* Current metadata schema version. Bump this when the metadata JSON structure
|
|
4
|
-
* changes in a way that requires read-time normalization.
|
|
5
|
-
*/
|
|
6
|
-
export declare const CURRENT_METADATA_VERSION = 1;
|
|
7
2
|
/**
|
|
8
3
|
* Normalizes flow metadata written by older SDK versions to the current schema.
|
|
9
4
|
*
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CURRENT_METADATA_VERSION = void 0;
|
|
4
3
|
exports.normalizeFlowMetadata = normalizeFlowMetadata;
|
|
5
|
-
|
|
6
|
-
* Current metadata schema version. Bump this when the metadata JSON structure
|
|
7
|
-
* changes in a way that requires read-time normalization.
|
|
8
|
-
*/
|
|
9
|
-
exports.CURRENT_METADATA_VERSION = 1;
|
|
4
|
+
const MetadataVersion_1 = require("../models/MetadataVersion");
|
|
10
5
|
/**
|
|
11
6
|
* Normalizes flow metadata written by older SDK versions to the current schema.
|
|
12
7
|
*
|
|
@@ -16,7 +11,7 @@ exports.CURRENT_METADATA_VERSION = 1;
|
|
|
16
11
|
* introduced in migration v9).
|
|
17
12
|
*/
|
|
18
13
|
function normalizeFlowMetadata(raw) {
|
|
19
|
-
if (raw.metadataVersion ===
|
|
14
|
+
if (raw.metadataVersion === MetadataVersion_1.CURRENT_METADATA_VERSION) {
|
|
20
15
|
return raw;
|
|
21
16
|
}
|
|
22
17
|
// Pre-v5 format: `browser` and `targetWebsite` at the top level, no `target`.
|