donobu 5.60.8 → 5.61.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/ai/locate/locateElement.js +11 -15
- package/dist/esm/managers/DonobuFlow.d.ts +11 -4
- package/dist/esm/managers/DonobuFlow.js +44 -10
- package/dist/esm/managers/DonobuFlowsManager.js +1 -7
- package/dist/esm/tools/AssertTool.js +14 -18
- package/dist/esm/tools/ReplayableInteraction.d.ts +12 -2
- package/dist/esm/tools/ReplayableInteraction.js +164 -134
- package/dist/esm/utils/AdvancedSelectorGenerator.d.ts +106 -0
- package/dist/esm/utils/AdvancedSelectorGenerator.js +396 -0
- package/dist/esm/utils/EnvVarSensitivity.d.ts +37 -0
- package/dist/esm/utils/EnvVarSensitivity.js +64 -0
- package/dist/esm/utils/TemplateInterpolator.d.ts +50 -0
- package/dist/esm/utils/TemplateInterpolator.js +146 -5
- package/dist/esm/utils/envReferencePromptSection.d.ts +25 -0
- package/dist/esm/utils/envReferencePromptSection.js +42 -0
- package/dist/lib/ai/locate/locateElement.js +11 -15
- package/dist/managers/DonobuFlow.d.ts +11 -4
- package/dist/managers/DonobuFlow.js +44 -10
- package/dist/managers/DonobuFlowsManager.js +1 -7
- package/dist/tools/AssertTool.js +14 -18
- package/dist/tools/ReplayableInteraction.d.ts +12 -2
- package/dist/tools/ReplayableInteraction.js +164 -134
- package/dist/utils/AdvancedSelectorGenerator.d.ts +106 -0
- package/dist/utils/AdvancedSelectorGenerator.js +396 -0
- package/dist/utils/EnvVarSensitivity.d.ts +37 -0
- package/dist/utils/EnvVarSensitivity.js +64 -0
- package/dist/utils/TemplateInterpolator.d.ts +50 -0
- package/dist/utils/TemplateInterpolator.js +146 -5
- package/dist/utils/envReferencePromptSection.d.ts +25 -0
- package/dist/utils/envReferencePromptSection.js +42 -0
- package/package.json +1 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TEMPLATE_DATA_PATTERN = void 0;
|
|
3
|
+
exports.ENV_VALUE_FILTERS = exports.TEMPLATE_DATA_PATTERN = void 0;
|
|
4
|
+
exports.parseFilteredExpression = parseFilteredExpression;
|
|
5
|
+
exports.applyEnvValueFilters = applyEnvValueFilters;
|
|
6
|
+
exports.envValueForms = envValueForms;
|
|
7
|
+
exports.extractEnvVarReferences = extractEnvVarReferences;
|
|
4
8
|
exports.resolveExpression = resolveExpression;
|
|
5
9
|
exports.interpolateString = interpolateString;
|
|
6
10
|
exports.interpolateObject = interpolateObject;
|
|
@@ -31,6 +35,136 @@ function formatValue(value) {
|
|
|
31
35
|
// Default case
|
|
32
36
|
return String(value);
|
|
33
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Allow-listed, pure string transforms usable as `{{ <path> | <filter> }}` in a
|
|
40
|
+
* template (e.g. `{{$.env.NAV | title}}`). They let a single env value render in
|
|
41
|
+
* the various forms it takes across a page (a tab text "Contact" vs an href
|
|
42
|
+
* "/contact" vs a slug "some-tab") while a recorded selector keeps the
|
|
43
|
+
* placeholder instead of a frozen literal.
|
|
44
|
+
*
|
|
45
|
+
* Keep this set SMALL and deterministic: it is the single source of truth used
|
|
46
|
+
* both at replay (to render a value) and at record time (to recognize that an
|
|
47
|
+
* on-page literal is a transformed form of an env value). Each is a total
|
|
48
|
+
* `string -> string` function — no arguments, no side effects.
|
|
49
|
+
*/
|
|
50
|
+
exports.ENV_VALUE_FILTERS = {
|
|
51
|
+
lower: (s) => s.toLowerCase(),
|
|
52
|
+
upper: (s) => s.toUpperCase(),
|
|
53
|
+
capitalize: (s) => (s.length ? s.charAt(0).toUpperCase() + s.slice(1) : s),
|
|
54
|
+
title: (s) => s.replace(/(^|\s)(\S)/g, (_, sp, c) => sp + c.toUpperCase()),
|
|
55
|
+
trim: (s) => s.trim(),
|
|
56
|
+
kebab: (s) => s
|
|
57
|
+
.toLowerCase()
|
|
58
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
59
|
+
.replace(/^-+|-+$/g, ''),
|
|
60
|
+
snake: (s) => s
|
|
61
|
+
.toLowerCase()
|
|
62
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
63
|
+
.replace(/^_+|_+$/g, ''),
|
|
64
|
+
urlEncode: (s) => encodeURIComponent(s),
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Split a template expression into its JSONPath part and any trailing
|
|
68
|
+
* `| filter | filter` chain. Splits only on top-level `|` — pipes inside
|
|
69
|
+
* brackets/parens or quotes (e.g. a JSONPath filter `[?(@.x == "a|b")]`) are left
|
|
70
|
+
* intact. The custom {@link queryJsonPath} dialect never uses `|`, so a top-level
|
|
71
|
+
* `|` is unambiguously a filter delimiter.
|
|
72
|
+
*/
|
|
73
|
+
function parseFilteredExpression(content) {
|
|
74
|
+
const segments = [];
|
|
75
|
+
let current = '';
|
|
76
|
+
let depth = 0;
|
|
77
|
+
let quote = null;
|
|
78
|
+
for (const ch of content) {
|
|
79
|
+
if (quote) {
|
|
80
|
+
current += ch;
|
|
81
|
+
if (ch === quote) {
|
|
82
|
+
quote = null;
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (ch === '"' || ch === "'") {
|
|
87
|
+
quote = ch;
|
|
88
|
+
current += ch;
|
|
89
|
+
}
|
|
90
|
+
else if (ch === '[' || ch === '(') {
|
|
91
|
+
depth++;
|
|
92
|
+
current += ch;
|
|
93
|
+
}
|
|
94
|
+
else if (ch === ']' || ch === ')') {
|
|
95
|
+
depth--;
|
|
96
|
+
current += ch;
|
|
97
|
+
}
|
|
98
|
+
else if (ch === '|' && depth === 0) {
|
|
99
|
+
segments.push(current);
|
|
100
|
+
current = '';
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
current += ch;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
segments.push(current);
|
|
107
|
+
const trimmed = segments.map((segment) => segment.trim());
|
|
108
|
+
return {
|
|
109
|
+
path: trimmed[0],
|
|
110
|
+
filters: trimmed.slice(1).filter((name) => name.length > 0),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Apply a chain of {@link ENV_VALUE_FILTERS} to a string, left to right. Throws
|
|
115
|
+
* on an unknown filter name so the caller (interpolation) leaves the original
|
|
116
|
+
* `{{...}}` placeholder in place rather than silently emitting a wrong value.
|
|
117
|
+
*/
|
|
118
|
+
function applyEnvValueFilters(value, filters) {
|
|
119
|
+
let result = value;
|
|
120
|
+
for (const name of filters) {
|
|
121
|
+
const filter = exports.ENV_VALUE_FILTERS[name];
|
|
122
|
+
if (!filter) {
|
|
123
|
+
throw new Error(`Unknown interpolation filter: '${name}'`);
|
|
124
|
+
}
|
|
125
|
+
result = filter(result);
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The distinct forms an env value can take via the allow-listed filters — the
|
|
131
|
+
* identity form plus each filter applied. Used at record time to recognize that
|
|
132
|
+
* an on-page literal (e.g. "Contact") is a transformed form of an env value
|
|
133
|
+
* (e.g. "contact") so the recorded selector can keep `{{$.env.X | title}}`.
|
|
134
|
+
*
|
|
135
|
+
* Identity comes first; duplicate forms (a filter that doesn't change the value)
|
|
136
|
+
* are dropped, preferring the simplest (earliest) producer.
|
|
137
|
+
*/
|
|
138
|
+
function envValueForms(value) {
|
|
139
|
+
const forms = [];
|
|
140
|
+
const seen = new Set();
|
|
141
|
+
const add = (filter, form) => {
|
|
142
|
+
if (form.length > 0 && !seen.has(form)) {
|
|
143
|
+
seen.add(form);
|
|
144
|
+
forms.push({ filter, form });
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
add(null, value);
|
|
148
|
+
for (const [name, filter] of Object.entries(exports.ENV_VALUE_FILTERS)) {
|
|
149
|
+
add(name, filter(value));
|
|
150
|
+
}
|
|
151
|
+
return forms;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Extract the env var NAMES referenced (via `{{$.env.NAME}}`, with or without a
|
|
155
|
+
* `| filter` suffix) anywhere in a template string. Centralizes the filter-aware
|
|
156
|
+
* parsing so callers don't re-derive names by naive substring math (which breaks
|
|
157
|
+
* once an expression carries a filter, e.g. `$.env.NAV | title`).
|
|
158
|
+
*/
|
|
159
|
+
function extractEnvVarReferences(template) {
|
|
160
|
+
const prefix = '$.env.';
|
|
161
|
+
const names = extractInterpolationExpressions(template)
|
|
162
|
+
.map((expression) => parseFilteredExpression(expression).path)
|
|
163
|
+
.filter((path) => path.startsWith(prefix))
|
|
164
|
+
.map((path) => path.substring(prefix.length).trim())
|
|
165
|
+
.filter((name) => name.length > 0);
|
|
166
|
+
return [...new Set(names)];
|
|
167
|
+
}
|
|
34
168
|
/**
|
|
35
169
|
* Resolve a JSONPath expression against the context
|
|
36
170
|
* @param expression - The JSONPath expression to evaluate
|
|
@@ -118,10 +252,17 @@ function interpolateString(template, dataSource, recursionDepth = 0, maxRecursio
|
|
|
118
252
|
try {
|
|
119
253
|
// If the expression contains nested templates, process it first, passing along depth info
|
|
120
254
|
const processedExpression = interpolateString(expressionContent, dataSource, recursionDepth + 1, maxRecursionDepth, new Set(processed));
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
//
|
|
124
|
-
const
|
|
255
|
+
// Split any trailing `| filter` chain off the (nested-resolved)
|
|
256
|
+
// expression, resolve the JSONPath part, then apply the filters to the
|
|
257
|
+
// rendered value.
|
|
258
|
+
const { path, filters } = parseFilteredExpression(processedExpression);
|
|
259
|
+
const value = resolveExpression(path, dataSource);
|
|
260
|
+
// If the value is undefined, keep the original expression (filters and
|
|
261
|
+
// all). An unknown filter throws and is caught below, also leaving the
|
|
262
|
+
// original placeholder intact rather than emitting a wrong value.
|
|
263
|
+
const replacement = value !== undefined
|
|
264
|
+
? applyEnvValueFilters(formatValue(value), filters)
|
|
265
|
+
: `{{${expressionContent}}}`;
|
|
125
266
|
// Store the replacement for later
|
|
126
267
|
processedSegments.push([openIndex, closeIndex, replacement]);
|
|
127
268
|
i = closeIndex;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared prompt guidance instructing the model to emit `{{$.env.NAME}}`
|
|
3
|
+
* placeholders — rather than baking the resolved literal — when a field's value
|
|
4
|
+
* comes from an env var the user referenced, so cached artifacts stay valid
|
|
5
|
+
* across runs with different env values. Includes the `| filter` transforms so
|
|
6
|
+
* the model can express a value that renders in a transformed form on the page.
|
|
7
|
+
*
|
|
8
|
+
* Shared by the locate and assert subsystems (each appends its own
|
|
9
|
+
* representation-specific rules/examples). Returns '' when no referenced env vars
|
|
10
|
+
* are in play, which keeps the prompt small for the common case.
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildEnvReferencePromptSection(options: {
|
|
13
|
+
/** Noun for the user's input, e.g. 'assertion' or 'description'. */
|
|
14
|
+
artifactNoun: string;
|
|
15
|
+
/** The raw, uninterpolated user input (caller decides any quoting). */
|
|
16
|
+
rawArtifact: string;
|
|
17
|
+
/**
|
|
18
|
+
* Where placeholders may be emitted, e.g.
|
|
19
|
+
* "any LocatorStep `text`, `name`, or `testId` field".
|
|
20
|
+
*/
|
|
21
|
+
fieldGuidance: string;
|
|
22
|
+
/** Referenced env vars and their current resolved values. */
|
|
23
|
+
referencedEnvEntries: Array<[string, string]>;
|
|
24
|
+
}): string;
|
|
25
|
+
//# sourceMappingURL=envReferencePromptSection.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildEnvReferencePromptSection = buildEnvReferencePromptSection;
|
|
4
|
+
const TemplateInterpolator_1 = require("./TemplateInterpolator");
|
|
5
|
+
/**
|
|
6
|
+
* Shared prompt guidance instructing the model to emit `{{$.env.NAME}}`
|
|
7
|
+
* placeholders — rather than baking the resolved literal — when a field's value
|
|
8
|
+
* comes from an env var the user referenced, so cached artifacts stay valid
|
|
9
|
+
* across runs with different env values. Includes the `| filter` transforms so
|
|
10
|
+
* the model can express a value that renders in a transformed form on the page.
|
|
11
|
+
*
|
|
12
|
+
* Shared by the locate and assert subsystems (each appends its own
|
|
13
|
+
* representation-specific rules/examples). Returns '' when no referenced env vars
|
|
14
|
+
* are in play, which keeps the prompt small for the common case.
|
|
15
|
+
*/
|
|
16
|
+
function buildEnvReferencePromptSection(options) {
|
|
17
|
+
const { artifactNoun, rawArtifact, fieldGuidance, referencedEnvEntries } = options;
|
|
18
|
+
if (referencedEnvEntries.length === 0) {
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
const mapping = referencedEnvEntries
|
|
22
|
+
.map(([name, value]) => ` - {{$.env.${name}}} = ${JSON.stringify(value)}`)
|
|
23
|
+
.join('\n');
|
|
24
|
+
const filters = Object.keys(TemplateInterpolator_1.ENV_VALUE_FILTERS).join(', ');
|
|
25
|
+
return `
|
|
26
|
+
|
|
27
|
+
The user's original ${artifactNoun} contains environment variable references using
|
|
28
|
+
the syntax \`{{$.env.NAME}}\`. To keep cached results valid across runs with
|
|
29
|
+
different env values, you MUST emit those same placeholders in ${fieldGuidance}
|
|
30
|
+
whose contents come from an env var. Do NOT bake the literal current value in.
|
|
31
|
+
|
|
32
|
+
When the on-page text is a deterministic transform of the value (different case,
|
|
33
|
+
spaces vs dashes, etc.), append a filter: \`{{$.env.NAME | filter}}\`. Available
|
|
34
|
+
filters: ${filters} (e.g. value "contact" shown as "Contact" -> {{$.env.NAME | capitalize}}).
|
|
35
|
+
|
|
36
|
+
Original (uninterpolated) ${artifactNoun}: ${rawArtifact}
|
|
37
|
+
|
|
38
|
+
Current env mapping (use these to identify which substrings on the page came from
|
|
39
|
+
which env var, then emit the placeholder rather than the literal):
|
|
40
|
+
${mapping}`;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=envReferencePromptSection.js.map
|