@openui5/sap.ui.testrecorder 1.145.1 → 1.147.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/REUSE.toml CHANGED
@@ -613,57 +613,6 @@ SPDX-License-Identifier = "MIT"
613
613
  SPDX-FileComment = "these files belong to: hammer.js"
614
614
 
615
615
 
616
- # Library: sap.ui.webc.common:
617
-
618
- [[annotations]]
619
- path = [
620
- "src/sap.ui.webc.common/src/sap/ui/webc/common/thirdparty/base/**",
621
- "src/sap.ui.webc.common/src/sap/ui/webc/common/thirdparty/theming/**",
622
- "src/sap.ui.webc.common/src/sap/ui/webc/common/thirdparty/localization/**",
623
- "src/sap.ui.webc.common/src/sap/ui/webc/common/thirdparty/icons/**",
624
- "src/sap.ui.webc.common/src/sap/ui/webc/common/thirdparty/icons-tnt/**",
625
- "src/sap.ui.webc.common/src/sap/ui/webc/common/thirdparty/icons-business-suite/**"
626
- ]
627
- precedence = "aggregate"
628
- SPDX-FileCopyrightText = "SAP"
629
- SPDX-License-Identifier = "Apache-2.0"
630
- SPDX-FileComment = "these files belong to: UI5 Web Components"
631
-
632
- [[annotations]]
633
- path = "src/sap.ui.webc.common/src/sap/ui/webc/common/thirdparty/lit-html/**"
634
- precedence = "aggregate"
635
- SPDX-FileCopyrightText = "Google LLC"
636
- SPDX-License-Identifier = "BSD-3-Clause"
637
- SPDX-FileComment = "these files belong to: lit-html"
638
-
639
-
640
- # Library: sap.ui.webc.fiori:
641
-
642
- [[annotations]]
643
- path = "src/sap.ui.webc.fiori/src/sap/ui/webc/fiori/thirdparty/**"
644
- precedence = "aggregate"
645
- SPDX-FileCopyrightText = "SAP"
646
- SPDX-License-Identifier = "Apache-2.0"
647
- SPDX-FileComment = "these files belong to: UI5 Web Components"
648
-
649
- [[annotations]]
650
- path = "src/sap.ui.webc.fiori/src/sap/ui/webc/fiori/lib/zxing.js"
651
- precedence = "aggregate"
652
- SPDX-FileCopyrightText = "2005 Sun Microsystems, Inc.; 2010-2014 University of Manchester; 2010-2015 Stian Soiland-Reyes; 2015 Peter Hull"
653
- SPDX-License-Identifier = "Apache-2.0"
654
- SPDX-FileComment = "these files belong to: ZXing"
655
-
656
-
657
- # Library: sap.ui.webc.main:
658
-
659
- [[annotations]]
660
- path = "src/sap.ui.webc.main/src/sap/ui/webc/main/thirdparty/**"
661
- precedence = "aggregate"
662
- SPDX-FileCopyrightText = "SAP"
663
- SPDX-License-Identifier = "Apache-2.0"
664
- SPDX-FileComment = "these files belong to: UI5 Web Components"
665
-
666
-
667
616
  # Outside of Libraries:
668
617
 
669
618
  [[annotations]]
package/THIRDPARTY.txt CHANGED
@@ -6,31 +6,31 @@ The full text of all referenced licenses is appended at the end of this file.
6
6
 
7
7
  Library: sap.f:
8
8
 
9
- Component: UI5 Web Components, version: 2.15.0
9
+ Component: UI5 Web Components, version: 2.19.2
10
10
  Copyright: SAP
11
11
  License: Apache-2.0
12
12
  License Text: https://github.com/UI5/openui5/blob/master/LICENSES/Apache-2.0.txt
13
13
  Contained in: src/sap.f/src/sap/f/thirdparty/**
14
14
 
15
- Component: UI5 Web Components Fiori, version: 2.15.0
15
+ Component: UI5 Web Components Fiori, version: 2.19.2
16
16
  Copyright: SAP
17
17
  License: Apache-2.0
18
18
  License Text: https://github.com/UI5/openui5/blob/master/LICENSES/Apache-2.0.txt
19
19
  Contained in: src/sap.f/src/sap/f/thirdparty/**
20
20
 
21
- Component: UI5 Web Components Icons, version: 2.15.0
21
+ Component: UI5 Web Components Icons, version: 2.19.2
22
22
  Copyright: SAP
23
23
  License: Apache-2.0
24
24
  License Text: https://github.com/UI5/openui5/blob/master/LICENSES/Apache-2.0.txt
25
25
  Contained in: src/sap.f/src/sap/f/thirdparty/**
26
26
 
27
- Component: UI5 Web Components Icons Business Suite, version: 2.15.0
27
+ Component: UI5 Web Components Icons Business Suite, version: 2.19.2
28
28
  Copyright: SAP
29
29
  License: Apache-2.0
30
30
  License Text: https://github.com/UI5/openui5/blob/master/LICENSES/Apache-2.0.txt
31
31
  Contained in: src/sap.f/src/sap/f/thirdparty/**
32
32
 
33
- Component: UI5 Web Components Icons TNT, version: 2.15.0
33
+ Component: UI5 Web Components Icons TNT, version: 2.19.2
34
34
  Copyright: SAP
35
35
  License: Apache-2.0
36
36
  License Text: https://github.com/UI5/openui5/blob/master/LICENSES/Apache-2.0.txt
@@ -446,50 +446,6 @@ License Text: https://github.com/UI5/openui5/blob/master/LICENSES/MIT.txt
446
446
  Contained in: src/sap.ui.mdc/test/sap/ui/mdc/demokit/sample/Chart/ChartJS/control/hammerjs.js
447
447
 
448
448
 
449
- Library: sap.ui.webc.common:
450
-
451
- Component: UI5 Web Components, version: 1.18.0
452
- Copyright: SAP
453
- License: Apache-2.0
454
- License Text: https://github.com/UI5/openui5/blob/master/LICENSES/Apache-2.0.txt
455
- Contained in: src/sap.ui.webc.common/src/sap/ui/webc/common/thirdparty/base/**
456
- src/sap.ui.webc.common/src/sap/ui/webc/common/thirdparty/theming/**
457
- src/sap.ui.webc.common/src/sap/ui/webc/common/thirdparty/localization/**
458
- src/sap.ui.webc.common/src/sap/ui/webc/common/thirdparty/icons/**
459
- src/sap.ui.webc.common/src/sap/ui/webc/common/thirdparty/icons-tnt/**
460
- src/sap.ui.webc.common/src/sap/ui/webc/common/thirdparty/icons-business-suite/**
461
-
462
- Component: lit-html, version: 2.2.2
463
- Copyright: Google LLC
464
- License: BSD-3-Clause
465
- License Text: https://github.com/UI5/openui5/blob/master/LICENSES/BSD-3-Clause.txt
466
- Contained in: src/sap.ui.webc.common/src/sap/ui/webc/common/thirdparty/lit-html/**
467
-
468
-
469
- Library: sap.ui.webc.fiori:
470
-
471
- Component: UI5 Web Components, version: 1.18.0
472
- Copyright: SAP
473
- License: Apache-2.0
474
- License Text: https://github.com/UI5/openui5/blob/master/LICENSES/Apache-2.0.txt
475
- Contained in: src/sap.ui.webc.fiori/src/sap/ui/webc/fiori/thirdparty/**
476
-
477
- Component: ZXing, version: 0.17.1
478
- Copyright: 2005 Sun Microsystems, Inc.; 2010-2014 University of Manchester; 2010-2015 Stian Soiland-Reyes; 2015 Peter Hull
479
- License: Apache-2.0
480
- License Text: https://github.com/UI5/openui5/blob/master/LICENSES/Apache-2.0.txt
481
- Contained in: src/sap.ui.webc.fiori/src/sap/ui/webc/fiori/lib/zxing.js
482
-
483
-
484
- Library: sap.ui.webc.main:
485
-
486
- Component: UI5 Web Components, version: 1.18.0
487
- Copyright: SAP
488
- License: Apache-2.0
489
- License Text: https://github.com/UI5/openui5/blob/master/LICENSES/Apache-2.0.txt
490
- Contained in: src/sap.ui.webc.main/src/sap/ui/webc/main/thirdparty/**
491
-
492
-
493
449
  Outside of Libraries:
494
450
 
495
451
  Component: JSDoc 3, version: 3.6.7
@@ -498,7 +454,7 @@ License: Apache-2.0
498
454
  License Text: https://github.com/UI5/openui5/blob/master/LICENSES/Apache-2.0.txt
499
455
  Contained in: lib/jsdoc/ui5/plugin.js
500
456
 
501
- Component: SAP Theming Base Content, version: 11.32.2-20250827135846+79bb30d311678435d61b21db26ecbe07d6b05079
457
+ Component: SAP Theming Base Content, version: 11.34.0
502
458
  Copyright: SAP SE or an SAP affiliate company and Theming Base Content contributors
503
459
  License: Apache-2.0
504
460
  License Text: https://github.com/UI5/openui5/blob/master/LICENSES/Apache-2.0.txt
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openui5/sap.ui.testrecorder",
3
- "version": "1.145.1",
3
+ "version": "1.147.0",
4
4
  "description": "OpenUI5 UI Library sap.ui.testrecorder",
5
5
  "author": "SAP SE (https://www.sap.com)",
6
6
  "license": "Apache-2.0",
@@ -14,10 +14,10 @@
14
14
  "url": "https://github.com/UI5/openui5.git"
15
15
  },
16
16
  "dependencies": {
17
- "@openui5/sap.m": "1.145.1",
18
- "@openui5/sap.ui.codeeditor": "1.145.1",
19
- "@openui5/sap.ui.core": "1.145.1",
20
- "@openui5/sap.ui.layout": "1.145.1",
21
- "@openui5/sap.ui.support": "1.145.1"
17
+ "@openui5/sap.m": "1.147.0",
18
+ "@openui5/sap.ui.codeeditor": "1.147.0",
19
+ "@openui5/sap.ui.core": "1.147.0",
20
+ "@openui5/sap.ui.layout": "1.147.0",
21
+ "@openui5/sap.ui.support": "1.147.0"
22
22
  }
23
23
  }
@@ -6,7 +6,7 @@
6
6
  <copyright>OpenUI5
7
7
  * (c) Copyright 2026 SAP SE or an SAP affiliate company.
8
8
  * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.</copyright>
9
- <version>1.145.1</version>
9
+ <version>1.147.0</version>
10
10
 
11
11
  <documentation>UI5 library: sap.ui.testrecorder</documentation>
12
12
 
@@ -0,0 +1,303 @@
1
+ /*!
2
+ * OpenUI5
3
+ * (c) Copyright 2026 SAP SE or an SAP affiliate company.
4
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
5
+ */
6
+ sap.ui.define([
7
+ "sap/base/Log",
8
+ "sap/ui/base/ManagedObject",
9
+ "sap/ui/core/Element",
10
+ "sap/base/util/merge",
11
+ "sap/ui/test/Opa5",
12
+ "sap/ui/test/RecordReplay",
13
+ "sap/ui/test/actions/Press",
14
+ "sap/ui/test/actions/EnterText",
15
+ "sap/ui/testrecorder/inspector/ControlAPI",
16
+ "sap/ui/testrecorder/inspector/ControlInspector",
17
+ "sap/ui/testrecorder/utils/filterControlTree",
18
+ "sap/ui/testrecorder/utils/convertTreeToMarkdown"
19
+ ], function (Log, ManagedObject, Element, merge, Opa5, RecordReplay, Press, EnterText, ControlAPI, ControlInspector, filterControlTree, convertTreeToMarkdown) {
20
+ "use strict";
21
+
22
+ var oControlTree = null;
23
+
24
+ var NODE_ID_REGEX = /^(\d+)_(\d+)$/;
25
+
26
+ const TREE_CONFIG = {
27
+ filter: {
28
+ includeAncestors: true,
29
+ includeDescendants: true,
30
+ includeInvisibleText: false
31
+ },
32
+ nodeData: {
33
+ includeAssignedProperties: true,
34
+ includeAssignedAssociations: true,
35
+ includeTooltipText: true
36
+ },
37
+ output: {
38
+ verbose: false
39
+ }
40
+ };
41
+
42
+ /**
43
+ * @class A singleton that provides methods to interact with the UI5 control tree, such as searching for controls, performing actions, and retrieving control information.
44
+ * @extends sap.ui.base.ManagedObject
45
+ * @alias sap.ui.testrecorder.ControlTree
46
+ * @private
47
+ * @since 1.147
48
+ */
49
+ var ControlTree = ManagedObject.extend("sap.ui.testrecorder.ControlTree", {
50
+ constructor: function () {
51
+ if (!oControlTree) {
52
+ ManagedObject.apply(this, arguments);
53
+ this._oNodeIdToControlIdMap = {};
54
+ this._iSnapshotsCount = 0;
55
+ this._iNodesCount = 0; // count of nodes in last snapshot
56
+ ControlInspector.updateSettings({
57
+ preferViewId: true, // prefer view-relative control id over global control id
58
+ preferViewNameAsViewLocator: true,
59
+ separateViewNamespace: true,
60
+ preferChainableSnippets: true
61
+ });
62
+ } else {
63
+ Log.warning("Only one ControlTree allowed");
64
+ return oControlTree;
65
+ }
66
+ }
67
+ });
68
+
69
+ /**
70
+ * @typedef {sap.ui.core.support.ToolsAPI.ControlTreeNode} sap.ui.testrecorder.ControlTreeNode
71
+ * @description Extends {@link sap.ui.core.support.ToolsAPI.ControlTreeNode} with snapshot-relative identifier
72
+ * for API consumers to retrieve detailed control information.
73
+ * @property {string} [nodeId] - Snapshot-relative identifier (format: "snapshotNumber_nodeNumber").
74
+ * Can be passed to getControlData() to identify the control.
75
+ * @private
76
+ * @since 1.147
77
+ */
78
+
79
+ /**
80
+ * Search for controls matching the provided query
81
+ * @param {string} sQuery The search query string
82
+ * @returns {Promise<String>} A promise that resolves with the matched subtree formatted as a markdown string
83
+ * @private
84
+ * @since 1.147
85
+ */
86
+ ControlTree.prototype.search = function (sQuery) {
87
+ var aTree = ControlAPI.getAllControlData(TREE_CONFIG.nodeData).renderedControls;
88
+
89
+ var oFilterOptions = merge({ query: sQuery }, TREE_CONFIG.filter);
90
+ var aFilteredTree = filterControlTree(aTree, oFilterOptions);
91
+
92
+ // assign nodeIds to survivors
93
+ this._assignNodeIdsToTree(aFilteredTree);
94
+
95
+ // to output format
96
+ return Promise.resolve(convertTreeToMarkdown(aFilteredTree, {
97
+ verbose: TREE_CONFIG.output.verbose
98
+ }));
99
+ };
100
+
101
+ /**
102
+ * Obtains detailed information about a control identified by the given node ID, including a selector snippet and customized control data
103
+ * @param {string} sNodeId
104
+ * @returns {Promise<object>} A promise that resolves with an object containing the control information
105
+ * @private
106
+ * @since 1.147
107
+ */
108
+ ControlTree.prototype.getControlData = function (sId) {
109
+ var sControlId = this._getControlId(sId),
110
+ sControlType = this._getControlType(sControlId),
111
+ oControlData = ControlAPI.getControlData({
112
+ controlId: sControlId,
113
+ includeAggregations: true,
114
+ includeAssociations: true
115
+ }),
116
+ oPrunedData = this._pruneControlData(oControlData, {
117
+ inheritedFrom: false,
118
+ type: false,
119
+ bindings: false
120
+ });
121
+
122
+ return ControlInspector._getCodeSnippet({ domElementId: sControlId })
123
+ .then(function (sSnippet) {
124
+ return Object.assign({
125
+ controlId: sControlId,
126
+ controlType: sControlType,
127
+ selectorSnippet: sSnippet
128
+ }, oPrunedData);
129
+ });
130
+ };
131
+
132
+ /**
133
+ * Presses a control identified by the given node ID and returns the corresponding test action snippet
134
+ * @param {string} sNodeId The ID of the control to press
135
+ * @param {object} oSettings optional settings for the press action
136
+ * @param {boolean} [oSettings.altKey=false] Press with Alt key modifier
137
+ * @param {boolean} [oSettings.ctrlKey=false] Press with Ctrl key modifier
138
+ * @param {boolean} [oSettings.shiftKey=false] Press with Shift key modifier
139
+ * @param {float} [oSettings.xPercentage=0] Press with X coordinate (0-100% of control width)
140
+ * @param {float} [oSettings.yPercentage=0] Press with Y coordinate (0-100% of control height)
141
+ * @param {boolean} [oSettings.keyDown=false] Dispatch a <code>keydown</code> keyboard event instead of mouse events; modifier keys (shiftKey, altKey, ctrlKey) are applied if set
142
+ * @param {boolean} [oSettings.keyUp=false] Dispatch a <code>keyup</code> keyboard event instead of mouse events; modifier keys (shiftKey, altKey, ctrlKey) are applied if set
143
+ * @param {boolean} [oSettings.rightClick=false] Trigger a right-click (context menu) event instead of a left-click, dispatching <code>mousedown</code> and <code>mouseup</code> with button 2 followed by a <code>contextmenu</code> event
144
+ * @returns {Promise<string>} The generated test action snippet for the press action
145
+ * @private
146
+ * @since 1.147
147
+ */
148
+ ControlTree.prototype.press = function (sNodeId, oSettings) {
149
+ var sActionSnippet, sControlId = this._getControlId(sNodeId);
150
+
151
+ return ControlInspector._getCodeSnippet({ domElementId: sControlId, action: "PRESS", actionSettings: oSettings })
152
+ .then(function (snippet) {
153
+ sActionSnippet = snippet;
154
+ return this._executeAction(sControlId, Press, oSettings);
155
+ }.bind(this)).then(function () {
156
+ return sActionSnippet;
157
+ });
158
+ };
159
+
160
+ /**
161
+ * Enters text into a control identified by the given node ID and returns the corresponding test action snippet
162
+ * @param {string} sNodeId
163
+ * @param {object} oSettings settings for entering text
164
+ * @param {string} oSettings.text the text to enter (required)
165
+ * @param {boolean} oSettings.submitText whether to submit the text after entering (optional, default: false)
166
+ * @param {boolean} oSettings.clearTextFirst whether to clear existing text before entering new text (optional, default: false)
167
+ * @returns {Promise<string>} A promise that resolves with the corresponding test action snippet
168
+ * @private
169
+ * @since 1.147
170
+ */
171
+ ControlTree.prototype.enterText = function (sNodeId, oSettings) {
172
+ var sActionSnippet, sControlId = this._getControlId(sNodeId);
173
+
174
+ this._adaptEnterTextSettings(oSettings);
175
+
176
+ return ControlInspector._getCodeSnippet({ domElementId: sControlId, action: "ENTER_TEXT", actionSettings: oSettings })
177
+ .then(function (snippet) {
178
+ sActionSnippet = snippet;
179
+ return this._executeAction(sControlId, EnterText, oSettings);
180
+ }.bind(this)).then(function () {
181
+ return sActionSnippet;
182
+ });
183
+ };
184
+
185
+ ControlTree.prototype._executeAction = function (sControlId, ActionClass, oSettings) {
186
+ return ControlInspector.getSelector({ domElementId: sControlId })
187
+ .then(function (oSelector) {
188
+ oSettings = this._refineActionSettings(oSelector, oSettings);
189
+ oSelector.actions = new ActionClass(oSettings);
190
+
191
+ var oPromise = new Opa5().waitFor(oSelector); // enqueue
192
+ Opa5.emptyQueue(); // trigger execution of enqueued `waitFor`s
193
+ return oPromise.then(function () {
194
+ return RecordReplay.waitForUI5();
195
+ });
196
+ }.bind(this));
197
+ };
198
+
199
+ ControlTree.prototype._refineActionSettings = function (oSelector, oSettings) {
200
+ oSettings = oSettings || {};
201
+ var sIdSuffix = oSelector.interaction?.idSuffix;
202
+ if (sIdSuffix) {
203
+ oSettings.idSuffix = sIdSuffix;
204
+ }
205
+ return oSettings;
206
+ };
207
+
208
+ /**
209
+ * Walk the filtered tree and assign nodeIds to all control nodes.
210
+ * @param {Array<sap.ui.testrecorder.ControlTreeNode>} aNodes - Array of filtered tree nodes
211
+ * @private
212
+ */
213
+ ControlTree.prototype._assignNodeIdsToTree = function (aNodes) {
214
+ this._iSnapshotsCount++;
215
+ this._iNodesCount = 0;
216
+
217
+ var assignRecursive = function (aInnerNodes) {
218
+ for (var i = 0; i < aInnerNodes.length; i++) {
219
+ this._assignNodeId(aInnerNodes[i]);
220
+ if (aInnerNodes[i].content && aInnerNodes[i].content.length) {
221
+ assignRecursive(aInnerNodes[i].content);
222
+ }
223
+ }
224
+ }.bind(this);
225
+
226
+ assignRecursive(aNodes);
227
+ };
228
+
229
+ /**
230
+ * Assign a unique nodeId to a control node.
231
+ * Stores the mapping between nodeId and control ID for later retrieval.
232
+ * @param {sap.ui.testrecorder.ControlTreeNode} oNode - Node to enhance (mutated)
233
+ * @private
234
+ */
235
+ ControlTree.prototype._assignNodeId = function (oNode) {
236
+ if (oNode.type === 'sap-ui-control') {
237
+ var sNodeId = this._iSnapshotsCount + "_" + (++this._iNodesCount);
238
+ oNode.nodeId = sNodeId;
239
+ this._oNodeIdToControlIdMap[sNodeId] = oNode.id;
240
+ }
241
+ };
242
+
243
+ /**
244
+ * Retrieve original control id for a previously assigned nodeId or return the raw controlId as-is.
245
+ * @param {string} sId - nodeId (format: "snapshotNumber_nodeNumber") or raw controlId
246
+ * @returns {string|null} original control id or null when not found
247
+ */
248
+ ControlTree.prototype._getControlId = function (sId) {
249
+ if (typeof sId !== "string") {
250
+ return null;
251
+ }
252
+
253
+ return NODE_ID_REGEX.test(sId) ? (this._oNodeIdToControlIdMap[sId] || null) : sId;
254
+ };
255
+
256
+ /**
257
+ * Retrieve control type name for either a nodeId or a raw controlId.
258
+ * When given a nodeId, resolves it to a controlId via the map first.
259
+ * @param {string} sControlId - control id
260
+ * @returns {string|null} control type name or null when not found
261
+ */
262
+ ControlTree.prototype._getControlType = function (sControlId) {
263
+ var oControl = Element.getElementById(sControlId);
264
+ return oControl ? oControl.getMetadata().getName() : null;
265
+ };
266
+
267
+ ControlTree.prototype._pruneControlData = function (oControlData, oOptions) {
268
+ var fnPrune = function (oProperty) {
269
+ if (oOptions.inheritedFrom === false) {
270
+ delete oProperty.inheritedFrom;
271
+ }
272
+ if (oOptions.type === false) {
273
+ delete oProperty.type;
274
+ }
275
+ };
276
+
277
+ if (oOptions.bindings === false) {
278
+ delete oControlData.bindings;
279
+ }
280
+
281
+ oControlData.properties?.own.forEach(fnPrune);
282
+ oControlData.properties?.inherited.forEach(fnPrune);
283
+ oControlData.aggregations?.own.forEach(fnPrune);
284
+ oControlData.aggregations?.inherited.forEach(fnPrune);
285
+ oControlData.associations?.own.forEach(fnPrune);
286
+ oControlData.associations?.inherited.forEach(fnPrune);
287
+
288
+ return oControlData;
289
+ };
290
+
291
+ // adapt the more AI-friendly property name to the underlying legacy property name
292
+ ControlTree.prototype._adaptEnterTextSettings = function (oSettings) {
293
+ if (oSettings.submitText === false) { // adapt to underlying API
294
+ oSettings.keepFocus = true;
295
+ }
296
+ delete oSettings.submitText;
297
+ };
298
+
299
+ oControlTree = new ControlTree();
300
+
301
+ return oControlTree;
302
+
303
+ }, true);
@@ -36,6 +36,7 @@ sap.ui.define([
36
36
  * @param {object} mData data from which to generate a snippet
37
37
  * @param {object} mData.controlSelector control selector in string format
38
38
  * @param {string} mData.action name of the action to record for the control
39
+ * @param {string} mData.actionSettings settings for the action constructor
39
40
  * @returns {Promise<string>} Promise for a code snippet or error
40
41
  */
41
42
  ControlSnippetProvider.prototype.getSnippet = function (mData) {
@@ -21,6 +21,7 @@ sap.ui.define([
21
21
  * @param {object} mData data from which to generate a snippet
22
22
  * @param {object} mData.controlSelector control selector in string format
23
23
  * @param {string} mData.action name of the action to record for the control
24
+ * @param {string} mData.actionSettings settings for the action constructor
24
25
  * @param {object} mData.assertion assertion details - property name, type and expected value
25
26
  * @param {object} mData.settings preferences for the snippet e.g. formatting, method wrapping
26
27
  * @param {boolean} mData.settings.formatAsPOMethod true if selectors should be wrapped in a page object method. Default value is true.
@@ -20,12 +20,17 @@ sap.ui.define([
20
20
  * @param {object} mData data from which to generate a snippet
21
21
  * @param {object} mData.controlSelector control selector in string format
22
22
  * @param {string} mData.action name of the action to record for the control
23
+ * @param {object} mData.actionSettings options for the action contructor
23
24
  * @param {object} mData.assertion assertion details - property name, type and expected value
24
25
  * @returns {string} a stringified code snippet
25
26
  */
26
27
  OPA5ControlSnippetGenerator.prototype._generate = function (mData) {
27
28
  var sIdSuffix = mData.controlSelector.interaction && mData.controlSelector.interaction.idSuffix;
28
- var sAction = this._getActionAsString(mData.action, sIdSuffix);
29
+ var oActionSettings = mData.actionSettings || {};
30
+ if (sIdSuffix) {
31
+ oActionSettings.idSuffix = sIdSuffix;
32
+ }
33
+ var sAction = this._getActionAsString(mData.action, oActionSettings);
29
34
  var sAssertion = this._getAssertionAsString(mData.assertion);
30
35
 
31
36
  if (sAction) {
@@ -41,20 +46,28 @@ sap.ui.define([
41
46
  var sBasicSelector = this._getSelectorAsString(mData.controlSelector);
42
47
  var sSelectorWithAction = this._getSelectorWithAction(sBasicSelector, sAction);
43
48
  var sFullSelector = this._getSelectorWithAssertion(sSelectorWithAction, sAssertion);
44
- return "this.waitFor(" + sFullSelector + ");";
49
+ var sReturnPrefix = mData.settings && mData.settings.preferChainableSnippets ? "return " : "";
50
+ return sReturnPrefix + "this.waitFor(" + sFullSelector + ");";
45
51
  };
46
52
 
47
- OPA5ControlSnippetGenerator.prototype._getActionAsString = function (sAction, sIdSuffix) {
48
- sIdSuffix = sIdSuffix ? 'idSuffix: "' + sIdSuffix + '"' : "";
49
- var sParams;
53
+ /**
54
+ * @param {string} sAction name of the action to record for the control
55
+ * @param {object} oSettings settings for the action constructor
56
+ * @returns {string} a stringified code snippet for the action
57
+ */
58
+ OPA5ControlSnippetGenerator.prototype._getActionAsString = function (sAction, oSettings) {
59
+ oSettings = oSettings || {};
60
+
50
61
  switch (sAction) {
51
62
  case Commands.PRESS:
52
- sParams = sIdSuffix && "{\n" + this._getIndentation(3) + sIdSuffix + "\n" + this._getIndentation(2) + "}";
53
- return "new Press(" + sParams + ")";
63
+ return "new Press(" + this._getSettingsAsString(oSettings, 2) + ")";
64
+
54
65
  case Commands.ENTER_TEXT:
55
- sParams = "{\n" + this._getIndentation(2) + (sIdSuffix && sIdSuffix + ",\n" + this._getIndentation(2)) +
56
- 'text: "test"' + "\n" + this._getIndentation(1) + "}";
57
- return "new EnterText(" + sParams + ")";
66
+ if (oSettings.text === undefined) {
67
+ oSettings.text = "test";
68
+ }
69
+ return "new EnterText(" + this._getSettingsAsString(oSettings, 2) + ")";
70
+
58
71
  default: return "";
59
72
  }
60
73
  };
@@ -75,6 +88,17 @@ sap.ui.define([
75
88
  }
76
89
  };
77
90
 
91
+ OPA5ControlSnippetGenerator.prototype._getSettingsAsString = function (oSettings, iIndentationLevel) {
92
+ if (!oSettings || Object.keys(oSettings).length === 0) {
93
+ return "";
94
+ }
95
+ return "{\n" + this._getIndentation(iIndentationLevel) +
96
+ Object.keys(oSettings).map(function(sKey) {
97
+ return sKey + ': "' + oSettings[sKey] + '"';
98
+ }).join(",\n" + this._getIndentation(iIndentationLevel + 1)) +
99
+ "\n" + this._getIndentation(iIndentationLevel - 1) + "}";
100
+ };
101
+
78
102
  OPA5ControlSnippetGenerator.prototype._getSelectorWithAction = function (sSelector, sAction) {
79
103
  return sSelector.replace("actions: []", "actions: " + sAction);
80
104
  };
@@ -25,13 +25,22 @@ sap.ui.define([
25
25
  };
26
26
  };
27
27
 
28
- ControlAPI.prototype.getAllControlData = function () {
29
- var renderedControls = ToolsAPI.getRenderedControlTree();
28
+ ControlAPI.prototype.getAllControlData = function (oOptions) {
29
+ var renderedControls = ToolsAPI.getRenderedControlTree(oOptions);
30
30
  return {
31
31
  renderedControls: renderedControls
32
32
  };
33
33
  };
34
34
 
35
+ /**
36
+ * Retrieves detailed information about a control based on the provided data.
37
+ * @param {object} mData The data containing control identification information
38
+ * @param {object} mData.controlId The ID of the control (optional)
39
+ * @param {object} mData.domElementId The ID of the DOM element associated with the control (optional)
40
+ * @param {object} mData.includeAggregations Whether to include aggregations in the control data (optional, default: false)
41
+ * @param {object} mData.includeAssociations Whether to include associations in the control data (optional, default: false)
42
+ * @returns {object} An object containing the control's properties and bindings
43
+ */
35
44
  ControlAPI.prototype.getControlData = function (mData) {
36
45
  var sControlId;
37
46
  if (mData.controlId) {
@@ -50,10 +59,18 @@ sap.ui.define([
50
59
  var aProperties = this._getFormattedProperties(sControlId);
51
60
  var aBindings = this._getFormattedBindings(sControlId);
52
61
 
53
- return {
62
+ var oData = {
54
63
  properties: aProperties,
55
64
  bindings: aBindings
56
65
  };
66
+
67
+ if (mData.includeAggregations) {
68
+ oData.aggregations = this._getFormattedAggregations(sControlId);
69
+ }
70
+ if (mData.includeAssociations) {
71
+ oData.associations = this._getFormattedAssociations(sControlId);
72
+ }
73
+ return oData;
57
74
  };
58
75
 
59
76
  ControlAPI.prototype._getFormattedProperties = function (sControlId) {
@@ -113,6 +130,56 @@ sap.ui.define([
113
130
  return aFormattedBindings;
114
131
  };
115
132
 
133
+ ControlAPI.prototype._getFormattedAggregations = function (sControlId) {
134
+ var oAllAggregations = ToolsAPI.getControlAggregations(sControlId);
135
+ oAllAggregations.own = [oAllAggregations.own];
136
+ var mFormattedAggregations = {};
137
+ ["own", "inherited"].forEach(function (sType) {
138
+ mFormattedAggregations[sType] = [];
139
+ oAllAggregations[sType].forEach(function (mAggregationsContainer) {
140
+ Object.keys(mAggregationsContainer.aggregations).forEach(function (sAggregation) {
141
+ var mAggregation = mAggregationsContainer.aggregations[sAggregation];
142
+ mFormattedAggregations[sType].push({
143
+ inheritedFrom: mAggregationsContainer.meta.controlName,
144
+ aggregation: sAggregation,
145
+ type: mAggregation.type,
146
+ count: mAggregation.count
147
+ });
148
+ });
149
+ });
150
+ });
151
+
152
+ return mFormattedAggregations;
153
+ };
154
+
155
+ ControlAPI.prototype._getFormattedAssociations = function (sControlId) {
156
+ var oAllAssociations = ToolsAPI.getControlAssociations(sControlId);
157
+ oAllAssociations.own = [oAllAssociations.own];
158
+ var mFormattedAssociations = {};
159
+ ["own", "inherited"].forEach(function (sType) {
160
+ mFormattedAssociations[sType] = [];
161
+ oAllAssociations[sType].forEach(function (mAssociationsContainer) {
162
+ Object.keys(mAssociationsContainer.associations).forEach(function (sAssociation) {
163
+ var mAssociation = mAssociationsContainer.associations[sAssociation];
164
+ var vValue = isManagedObject(mAssociation.value) ? mAssociation.value.getId() : mAssociation.value;
165
+ mFormattedAssociations[sType].push({
166
+ inheritedFrom: mAssociationsContainer.meta.controlName,
167
+ association: sAssociation,
168
+ type: mAssociation.type,
169
+ value: vValue
170
+ });
171
+ });
172
+ });
173
+ });
174
+
175
+ return mFormattedAssociations;
176
+ };
177
+
178
+ // utils
179
+ function isManagedObject(oValue) {
180
+ return oValue && typeof oValue === "object" && typeof oValue.isA === "function" && oValue.isA("sap.ui.base.ManagedObject");
181
+ }
182
+
116
183
  return new ControlAPI();
117
184
 
118
185
  }, true);
@@ -66,13 +66,19 @@ sap.ui.define([
66
66
  /**
67
67
  * send an event to the test recorder frame that has as payload:
68
68
  * the most basic data about the app - framework name + version and control IDs
69
+ * @param {Object} [oOptions] - Optional settings for enriching tree nodes.
70
+ * @param {boolean} [oOptions.includeAssignedProperties] - Whether to include assigned properties in each node.
71
+ * @param {boolean} [oOptions.includeAssignedAssociations] - Whether to include assigned associations in each node.
72
+ * @param {boolean} [oOptions.includeTooltipText] - Whether to include tooltip text in each node.
69
73
  */
70
- ControlInspector.prototype.getAllControlData = function () {
74
+ ControlInspector.prototype.getAllControlData = function (oOptions) {
75
+ var oRenderedControls = ControlAPI.getAllControlData(oOptions).renderedControls;
71
76
  CommunicationBus.publish(CommunicationChannels.RECEIVE_ALL_CONTROLS_DATA, {
72
- renderedControls: ControlAPI.getAllControlData().renderedControls,
77
+ renderedControls: oRenderedControls,
73
78
  framework: ControlAPI.getFrameworkData().framework
74
79
  });
75
80
  ControlInspectorRepo.clear();
81
+ return oRenderedControls;
76
82
  };
77
83
 
78
84
  /**
@@ -81,6 +87,8 @@ sap.ui.define([
81
87
  * @param {object} mData control identifier
82
88
  * @param {string} mData.controlId ID of the control to inspect
83
89
  * @param {string} mData.domElementId ID of a dom element from which the control is found (e.g. dom ref)
90
+ * @param {object} mData.includeAggregations Whether to include aggregations in the control data (optional, default: false)
91
+ * @param {object} mData.includeAssociations Whether to include associations in the control data (optional, default: false)
84
92
  */
85
93
  ControlInspector.prototype.getControlData = function (mData) {
86
94
  var oDomElement = mData.domElementId ? document.getElementById(mData.domElementId) : Element.getElementById(mData.controlId).getDomRef();
@@ -89,31 +97,29 @@ sap.ui.define([
89
97
 
90
98
  var mControlData = ControlAPI.getControlData(mData);
91
99
  CommunicationBus.publish(CommunicationChannels.RECEIVE_CONTROL_DATA, mControlData);
100
+ return mControlData;
92
101
  };
93
102
 
94
103
  /**
95
- * send an event to the test recorder frame that has as payload:
96
- * a generated code snippet for locating controls
104
+ * generate a code snippet for locating controls, without publishing to the communication bus.
97
105
  * @param {object} mData object containing control identifiers and actions
98
106
  * @param {string} mData.domElementId ID of a dom element from which the control is found (e.g. dom ref)
99
107
  * @param {string} mData.action name of an action to record in the snippet (e.g. press, enter text)
108
+ * @param {string} mData.actionSettings settings for the action contructor
100
109
  * @param {object} mData.assertion assertion details - property name, type and expected value
110
+ * @returns {Promise<string>} resolves with the generated code snippet
101
111
  */
102
- ControlInspector.prototype.getCodeSnippet = function (mData) {
103
- var mDataForGenerator = Object.assign({}, mData, {
104
- settings: mSelectorSettings
105
- });
106
- // find a cached selector or generate a new one
107
- var mControlSelector = ControlInspectorRepo.findSelector(mData.domElementId);
108
- var oSelectorPromise = mControlSelector ? Promise.resolve(mControlSelector) : ControlSelectorGenerator.getSelector(mDataForGenerator);
112
+ ControlInspector.prototype._getCodeSnippet = function (mData) {
113
+ var mControlSelector;
109
114
 
110
- return oSelectorPromise.then(function (mSelector) {
115
+ return this.getSelector(mData).then(function (mSelector) {
111
116
  mControlSelector = mSelector;
112
117
  // given the selector, generate a dialect-specific code snippet
113
118
  return CodeSnippetProvider.getSnippet({
114
119
  controlSelector: mSelector,
115
- action: mDataForGenerator.action,
116
- assertion: mDataForGenerator.assertion,
120
+ action: mData.action,
121
+ actionSettings: mData.actionSettings,
122
+ assertion: mData.assertion,
117
123
  settings: mSelectorSettings
118
124
  });
119
125
  }).then(function (sSnippet) {
@@ -131,7 +137,20 @@ sap.ui.define([
131
137
  assertion: mData.assertion
132
138
  }, mSelectorSettings), DialectRegistry.getActiveDialect() === Dialects.WDI5);
133
139
  }
134
- }).then(function (sSnippet) {
140
+ });
141
+ };
142
+
143
+ /**
144
+ * send an event to the test recorder frame that has as payload:
145
+ * a generated code snippet for locating controls
146
+ * @param {object} mData object containing control identifiers and actions
147
+ * @param {string} mData.domElementId ID of a dom element from which the control is found (e.g. dom ref)
148
+ * @param {string} mData.action name of an action to record in the snippet (e.g. press, enter text)
149
+ * @param {string} mData.actionSettings settings for the action contructor
150
+ * @param {object} mData.assertion assertion details - property name, type and expected value
151
+ */
152
+ ControlInspector.prototype.getCodeSnippet = function (mData) {
153
+ return this._getCodeSnippet(mData).then(function (sSnippet) {
135
154
  // here sSnippet contains the snippets for one or multiple controls
136
155
  CommunicationBus.publish(CommunicationChannels.RECEIVE_CODE_SNIPPET, {
137
156
  codeSnippet: sSnippet
@@ -139,11 +158,25 @@ sap.ui.define([
139
158
  }).catch(function (oError) {
140
159
  CommunicationBus.publish(CommunicationChannels.RECEIVE_CODE_SNIPPET, {
141
160
  error: "Could not generate code snippet for " + JSON.stringify(mData) + ". Details: " + oError,
142
- domElementId: mDataForGenerator.domElementId
161
+ domElementId: mData.domElementId
143
162
  });
144
163
  });
145
164
  };
146
165
 
166
+ /**
167
+ * find a cached selector or generate a new one
168
+ * @param {object} mData object containing control identifiers
169
+ * @param {string} mData.domElementId ID of a dom element from which the control is found (e.g. dom ref)
170
+ * @returns {Promise<object>} resolves with the control selector
171
+ */
172
+ ControlInspector.prototype.getSelector = function (mData) {
173
+ var mDataForGenerator = Object.assign({}, mData, {
174
+ settings: mSelectorSettings
175
+ });
176
+ var mControlSelector = ControlInspectorRepo.findSelector(mData.domElementId);
177
+ return mControlSelector ? Promise.resolve(mControlSelector) : ControlSelectorGenerator.getSelector(mDataForGenerator);
178
+ };
179
+
147
180
  /**
148
181
  * given a control identifier, highlight the control in the app
149
182
  * @param {object} mData control identifier
@@ -24,7 +24,7 @@ sap.ui.define([
24
24
  * @namespace
25
25
  * @alias sap.ui.testrecorder
26
26
  * @author SAP SE
27
- * @version 1.145.1
27
+ * @version 1.147.0
28
28
  * @since 1.74
29
29
  * @public
30
30
  */
@@ -39,7 +39,7 @@ sap.ui.define([
39
39
  controls: [],
40
40
  elements: [],
41
41
  noLibraryCSS: true,
42
- version: "1.145.1",
42
+ version: "1.147.0",
43
43
  extensions: {
44
44
  //Configuration used for rule loading of Support Assistant
45
45
  "sap.ui.support": {
@@ -16,7 +16,7 @@
16
16
  <contentMiddle>
17
17
  <Image src="../ui/images/Logo_O_22x22.png" densityAware="false"></Image>
18
18
  <Text text="{/iFrameTitle}" />
19
- <c:Icon src="sap-icon://full-screen" press="toggleMinimize" />
19
+ <c:Icon src="sap-icon://full-screen" press=".toggleMinimize" />
20
20
  </contentMiddle>
21
21
  </Bar>
22
22
  <Bar id="ttStandardHeaderBar" class="sapContrast ttPageBar ttStandardPageBar">
@@ -29,15 +29,15 @@
29
29
  </contentMiddle>
30
30
  <contentRight>
31
31
  <!-- <c:Icon src="sap-icon://sys-help" tooltip="{i18n>TestRecorder.TitleBar.Documentation.Tooltip}" /> -->
32
- <c:Icon src="sap-icon://settings" press="openSettingsDialog" tooltip="{i18n>TestRecorder.TitleBar.Window.Settings.Tooltip}"/>
32
+ <c:Icon src="sap-icon://settings" press=".openSettingsDialog" tooltip="{i18n>TestRecorder.TitleBar.Window.Settings.Tooltip}"/>
33
33
 
34
- <c:Icon src="sap-icon://minimize" press="toggleMinimize" tooltip="{i18n>TestRecorder.TitleBar.Window.Minimize.Tooltip}" visible="{/isInIframe}" />
35
- <c:Icon src="sap-icon://slim-arrow-down" press="dockBottom" tooltip="{i18n>TestRecorder.TitleBar.Window.Dock.Bottom.Tooltip}" />
36
- <c:Icon src="sap-icon://slim-arrow-left" press="dockLeft" tooltip="{i18n>TestRecorder.TitleBar.Window.Dock.Left.Tooltip}" />
37
- <c:Icon src="sap-icon://slim-arrow-right" press="dockRight" tooltip="{i18n>TestRecorder.TitleBar.Window.Dock.Right.Tooltip}" />
34
+ <c:Icon src="sap-icon://minimize" press=".toggleMinimize" tooltip="{i18n>TestRecorder.TitleBar.Window.Minimize.Tooltip}" visible="{/isInIframe}" />
35
+ <c:Icon src="sap-icon://slim-arrow-down" press=".dockBottom" tooltip="{i18n>TestRecorder.TitleBar.Window.Dock.Bottom.Tooltip}" />
36
+ <c:Icon src="sap-icon://slim-arrow-left" press=".dockLeft" tooltip="{i18n>TestRecorder.TitleBar.Window.Dock.Left.Tooltip}" />
37
+ <c:Icon src="sap-icon://slim-arrow-right" press=".dockRight" tooltip="{i18n>TestRecorder.TitleBar.Window.Dock.Right.Tooltip}" />
38
38
 
39
- <c:Icon src="sap-icon://popup-window" press="openWindow" tooltip="{i18n>TestRecorder.TitleBar.Window.New.Tooltip}" visible="{/isInIframe}"/>
40
- <c:Icon src="sap-icon://decline" press="close" tooltip="{i18n>TestRecorder.TitleBar.Window.Close.Tooltip}"/>
39
+ <c:Icon src="sap-icon://popup-window" press=".openWindow" tooltip="{i18n>TestRecorder.TitleBar.Window.New.Tooltip}" visible="{/isInIframe}"/>
40
+ <c:Icon src="sap-icon://decline" press=".close" tooltip="{i18n>TestRecorder.TitleBar.Window.Close.Tooltip}"/>
41
41
  </contentRight>
42
42
  </Bar>
43
43
  </l:fixContent>
@@ -73,7 +73,7 @@
73
73
  <c:Item key="{key}" text="{label}" />
74
74
  </Select>
75
75
  <Label text="{i18n>TestRecorder.Inspect.Snippet.MultiSwitch.Label}" labelFor="multiSwitch" class="sapUiSmallMarginEnd" />
76
- <Switch id="multiSwitch" state="{/settings/multipleSnippets}" type="AcceptReject" change="_onChangeMultiple"/>
76
+ <Switch id="multiSwitch" state="{/settings/multipleSnippets}" type="AcceptReject" change="._onChangeMultiple"/>
77
77
  </HBox>
78
78
  </VBox>
79
79
  <code:CodeEditor
@@ -90,8 +90,8 @@
90
90
  colorTheme="theme-ambiance"
91
91
  lineNumbers="false"/>
92
92
  <HBox justifyContent="End" alignItems="Center">
93
- <Button text="{i18n>TestRecorder.Inspect.Snippet.Copy.Text}" press="copyCodeSnippet"/>
94
- <Button text="{i18n>TestRecorder.Inspect.Snippet.Clear.Text}" press="clearCodeSnippet" class="sapUiSmallMarginBegin"/>
93
+ <Button text="{i18n>TestRecorder.Inspect.Snippet.Copy.Text}" press=".copyCodeSnippet"/>
94
+ <Button text="{i18n>TestRecorder.Inspect.Snippet.Clear.Text}" press=".clearCodeSnippet" class="sapUiSmallMarginBegin"/>
95
95
  </HBox>
96
96
  </ScrollContainer>
97
97
  </Panel>
@@ -129,7 +129,7 @@
129
129
  <c:Icon src="sap-icon://add-process"
130
130
  class="sapUiSmallMarginEnd"
131
131
  tooltip="{i18n>TestRecorder.Inspect.Properties.Property.Icon.Tooltip}"
132
- press="handlePropertyIconPress"/>
132
+ press=".handlePropertyIconPress"/>
133
133
  <Text text="{controls>property}"/>
134
134
  </HBox>
135
135
  <Text text="{controls>value}"/>
@@ -158,7 +158,7 @@
158
158
  <c:Icon src="sap-icon://add-process"
159
159
  class="sapUiSmallMarginEnd"
160
160
  tooltip="{i18n>TestRecorder.Inspect.Properties.Property.Icon.Tooltip}"
161
- press="handlePropertyIconPress"/>
161
+ press=".handlePropertyIconPress"/>
162
162
  <Text text="{controls>property}"/>
163
163
  </HBox>
164
164
  <Text text="{controls>value}"/>
@@ -0,0 +1,150 @@
1
+ /*!
2
+ * OpenUI5
3
+ * (c) Copyright 2026 SAP SE or an SAP affiliate company.
4
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
5
+ */
6
+ sap.ui.define([], function() {
7
+ "use strict";
8
+
9
+ // Constants
10
+ var PAGE_CONTENT_HEADER = "## Page content";
11
+ var DEFAULT_INDENT = " ";
12
+
13
+ var DEFAULT_OPTIONS = {
14
+ indent: DEFAULT_INDENT,
15
+ verbose: false
16
+ };
17
+
18
+ /**
19
+ * Converts a control tree structure into a markdown-formatted string representation.
20
+ *
21
+ * The function takes a tree structure (single node or array of nodes) and formats it
22
+ * as an indented markdown hierarchy showing each control's metadata and relationships.
23
+ *
24
+ * <b>Note:</b> No module from <code>sap/ui/testrecorder</code> should be used for productive coding!
25
+ *
26
+ * @alias module:sap/ui/testrecorder/utils/convertTreeToMarkdown
27
+ * @param {sap.ui.testrecorder.ControlTreeNode|Array<sap.ui.testrecorder.ControlTreeNode>} vTree - Control tree (single node or array of root nodes)
28
+ * @param {Object} [oOptions] - Formatting options
29
+ * @param {string} [oOptions.indent=" "] - Indentation string for nested levels
30
+ * @param {boolean} [oOptions.verbose=false] - Whether to include verbose output (full control IDs and class names)
31
+ * @returns {string} Markdown-formatted string representation of the control tree
32
+ * @private
33
+ * @since 1.147
34
+ */
35
+ function convertTreeToMarkdown(vTree, oOptions) {
36
+ var aRoots = Array.isArray(vTree) ? vTree.slice() : [vTree];
37
+ var oMergedOptions = Object.assign({}, DEFAULT_OPTIONS, oOptions);
38
+ var aLines = [PAGE_CONTENT_HEADER];
39
+
40
+ aRoots.forEach(function (oRoot) {
41
+ traverseNode(oRoot, 0, aLines, oMergedOptions);
42
+ });
43
+
44
+ return aLines.join("\n");
45
+ }
46
+
47
+ /**
48
+ * Recursively traverses a node and its children, appending formatted lines to output.
49
+ * @param {sap.ui.testrecorder.ControlTreeNode} oNode - Node to traverse
50
+ * @param {number} iLevel - Current indentation level
51
+ * @param {Array<string>} aOut - Output array to append lines to
52
+ * @param {Object} oOptions - Formatting options
53
+ * @private
54
+ */
55
+ function traverseNode(oNode, iLevel, aOut, oOptions) {
56
+ if (!oNode) {
57
+ return;
58
+ }
59
+
60
+ aOut.push(formatNodeLine(oNode, iLevel, oOptions));
61
+
62
+ // Process children
63
+ var aChildren = oNode.content || [];
64
+ for (var iIndex = 0; iIndex < aChildren.length; iIndex++) {
65
+ traverseNode(aChildren[iIndex], iLevel + 1, aOut, oOptions);
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Formats a single node line with indentation and attributes.
71
+ * @param {sap.ui.testrecorder.ControlTreeNode} oNode - Node to format
72
+ * @param {number} iLevel - Indentation level
73
+ * @param {Object} oOptions - Formatting options
74
+ * @returns {string} Formatted node line
75
+ * @private
76
+ */
77
+ function formatNodeLine(oNode, iLevel, oOptions) {
78
+ var aParts = [];
79
+
80
+ // ID token (only when verbose)
81
+ if (oOptions.verbose) {
82
+ aParts.push("id=" + oNode.id);
83
+ }
84
+
85
+ // Include control class name: full when verbose, otherwise only last segment
86
+ if (oNode.name) {
87
+ var sDisplayName = oOptions.verbose ? oNode.name : getSimpleName(oNode.name);
88
+ aParts.push(sDisplayName);
89
+ }
90
+
91
+ var oData = oNode.data || {};
92
+
93
+ Object.keys(oData).forEach(function (sKey) {
94
+ var vValue = oData[sKey];
95
+ if (typeof vValue === "boolean") {
96
+ if (vValue === true) {
97
+ // For true booleans, emit bare attribute (e.g. focused, hidden, busy)
98
+ aParts.push(sKey);
99
+ }
100
+ } else {
101
+ var sFormatted = formatAttrValue(vValue);
102
+ aParts.push(sKey + "=" + sFormatted);
103
+ }
104
+ });
105
+
106
+ // nodeId (snapshot-relative identifier for API consumers)
107
+ if (oNode.nodeId) {
108
+ aParts.push("nodeId=" + formatAttrValue(oNode.nodeId));
109
+ }
110
+
111
+ return oOptions.indent.repeat(iLevel) + aParts.join(" ");
112
+ }
113
+
114
+ /**
115
+ * Formats an attribute value for markdown output.
116
+ * @param {*} vValue - Value to format
117
+ * @returns {string|boolean} Formatted value (boolean true returns true, others return strings)
118
+ * @private
119
+ */
120
+ function formatAttrValue(vValue) {
121
+ if (typeof vValue === "boolean") {
122
+ // Boolean true -> print key alone; false -> key=false (handled by caller)
123
+ return vValue === true ? true : "false";
124
+ }
125
+ if (typeof vValue === "number") {
126
+ return String(vValue);
127
+ }
128
+ if (typeof vValue === "string") {
129
+ return '"' + vValue.replace(/"/g, '\\"') + '"';
130
+ }
131
+ if (vValue === undefined || vValue === null) {
132
+ return '""';
133
+ }
134
+ // Fallback for complex types
135
+ return '"' + JSON.stringify(vValue).replace(/"/g, '\\"') + '"';
136
+ }
137
+
138
+ /**
139
+ * Extracts the simple name from a qualified name by returning the last segment after the final dot.
140
+ * @param {string} sQualifiedName - Fully qualified name (e.g., "sap.m.Button")
141
+ * @returns {string} Simple name (e.g., "Button") or the original name if no dots are present
142
+ * @private
143
+ */
144
+ function getSimpleName(sQualifiedName) {
145
+ var aNameParts = String(sQualifiedName).split('.');
146
+ return aNameParts[aNameParts.length - 1] || sQualifiedName;
147
+ }
148
+
149
+ return convertTreeToMarkdown;
150
+ });
@@ -0,0 +1,176 @@
1
+ /*!
2
+ * OpenUI5
3
+ * (c) Copyright 2026 SAP SE or an SAP affiliate company.
4
+ * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
5
+ */
6
+ sap.ui.define([], function() {
7
+ "use strict";
8
+
9
+ /**
10
+ * Filters a control tree based on search query and options.
11
+ *
12
+ * Applies node-skip rules (e.g. InvisibleText exclusion) and, when a search query is
13
+ * present, prunes the tree so that only matching nodes (and optionally their ancestors
14
+ * and/or descendants) survive.
15
+ *
16
+ * <b>Note:</b> No module from <code>sap/ui/testrecorder</code> should be used for productive coding!
17
+ *
18
+ * @alias module:sap/ui/testrecorder/utils/filterControlTree
19
+ * @param {Array<sap.ui.core.support.ToolsAPI.ControlTreeNode>} aRawTree - Array of root control-tree nodes
20
+ * @param {Object} oOptions - Filter options
21
+ * @param {string} [oOptions.query] - Free-text query to match against node id, name, and data
22
+ * @param {boolean} [oOptions.includeAncestors=true] - Keep ancestor nodes when a descendant matches
23
+ * @param {boolean} [oOptions.includeDescendants=true] - Keep descendant nodes when a node matches
24
+ * @param {boolean} [oOptions.includeInvisibleText=false] - Whether to include InvisibleText controls
25
+ * @returns {Array<sap.ui.testrecorder.ControlTreeNode>} Filtered copy of the tree (original is not mutated)
26
+ * @private
27
+ * @since 1.147
28
+ */
29
+ function filterControlTree(aRawTree, oOptions) {
30
+ return aRawTree.map(function(oControlNode) {
31
+ return filterNode(oControlNode, oOptions, false);
32
+ }).filter(Boolean);
33
+ }
34
+
35
+ /**
36
+ * Filter a control node and its subtree based on search query and options.
37
+ * @param {sap.ui.core.support.ToolsAPI.ControlTreeNode} controlNode - Original tree node
38
+ * @param {Object} oOptions - Filter options
39
+ * @param {boolean} bAncestorMatched - Whether an ancestor matched the query (propagated down)
40
+ * @returns {sap.ui.testrecorder.ControlTreeNode|null} Filtered node or null if pruned
41
+ * @private
42
+ */
43
+ function filterNode(controlNode, oOptions, bAncestorMatched) {
44
+ if (!controlNode || shouldSkipNode(controlNode, oOptions)) {
45
+ return null;
46
+ }
47
+ var sQuery = oOptions.query;
48
+
49
+ // No query → keep entire subtree (only apply skip checks)
50
+ if (!sQuery || !sQuery.trim()) {
51
+ return Object.assign({}, controlNode, { content: copyChildren(controlNode, oOptions) });
52
+ }
53
+
54
+ var bThisNodeMatches = doesNodeMatchQuery(controlNode, sQuery);
55
+
56
+ // If this node (or an ancestor) matched and includeDescendants is on,
57
+ // keep the entire subtree below (only apply skip checks)
58
+ if ((bAncestorMatched || bThisNodeMatches) && oOptions.includeDescendants) {
59
+ return Object.assign({}, controlNode, { content: copyChildren(controlNode, oOptions) });
60
+ }
61
+
62
+ // Recurse children — only surviving children are kept
63
+ var aFilteredChildren = [];
64
+ if (controlNode.content && Array.isArray(controlNode.content)) {
65
+ for (var i = 0; i < controlNode.content.length; i++) {
66
+ var oChild = filterNode(controlNode.content[i], oOptions, bThisNodeMatches);
67
+ if (oChild) {
68
+ aFilteredChildren.push(oChild);
69
+ }
70
+ }
71
+ }
72
+
73
+ // Keep this node if it directly matches,
74
+ // or if includeAncestors is on and a descendant survived (i.e. matched)
75
+ var bHasMatchingDescendant = aFilteredChildren.length > 0;
76
+ if (bThisNodeMatches || (oOptions.includeAncestors && bHasMatchingDescendant)) {
77
+ return Object.assign({}, controlNode, { content: aFilteredChildren });
78
+ }
79
+
80
+ return null; // prune
81
+ }
82
+
83
+ /**
84
+ * Copy children of a node, applying only skip checks (no query filtering).
85
+ * Used when the entire subtree should be kept.
86
+ * @param {sap.ui.core.support.ToolsAPI.ControlTreeNode} controlNode - Parent node whose children to copy
87
+ * @param {Object} oOptions - Filter options
88
+ * @returns {Array<sap.ui.testrecorder.ControlTreeNode>} Copied children
89
+ * @private
90
+ */
91
+ function copyChildren(controlNode, oOptions) {
92
+ var aChildren = [];
93
+ if (controlNode.content && Array.isArray(controlNode.content)) {
94
+ for (var i = 0; i < controlNode.content.length; i++) {
95
+ var oChild = controlNode.content[i];
96
+ if (!oChild || shouldSkipNode(oChild, oOptions)) {
97
+ continue;
98
+ }
99
+ aChildren.push(Object.assign({}, oChild, { content: copyChildren(oChild, oOptions) }));
100
+ }
101
+ }
102
+ return aChildren;
103
+ }
104
+
105
+ /**
106
+ * Check if a node should be skipped during processing.
107
+ * @param {sap.ui.core.support.ToolsAPI.ControlTreeNode} controlNode - Control node to check
108
+ * @param {Object} oOptions - Filter options
109
+ * @returns {boolean} True if node should be skipped
110
+ * @private
111
+ */
112
+ function shouldSkipNode(controlNode, oOptions) {
113
+ // Skip InvisibleText controls unless explicitly included
114
+ return !oOptions.includeInvisibleText && isInvisibleText(controlNode);
115
+ }
116
+
117
+ /**
118
+ * Check if a single node matches the query (without checking descendants).
119
+ * @param {sap.ui.core.support.ToolsAPI.ControlTreeNode} oNode - Control node to check
120
+ * @param {string} sQuery - Query string to match against
121
+ * @returns {boolean} True if THIS node matches (ignoring descendants)
122
+ * @private
123
+ */
124
+ function doesNodeMatchQuery(oNode, sQuery) {
125
+ if (!oNode || typeof sQuery !== "string" || !sQuery.trim()) {
126
+ return false; // no match if no query
127
+ }
128
+ sQuery = sQuery.toLowerCase().trim();
129
+
130
+ function matches(value) {
131
+ return value != null && String(value).toLowerCase().indexOf(sQuery) !== -1;
132
+ }
133
+
134
+ return matches(oNode.id) || matches(oNode.name) || matchesObjectKeysOrValues(oNode.data, matches);
135
+ }
136
+
137
+ /**
138
+ * Checks if a control node represents an InvisibleText control.
139
+ * @param {sap.ui.core.support.ToolsAPI.ControlTreeNode} controlNode - Control node to check
140
+ * @returns {boolean} True if the node is an InvisibleText control
141
+ * @private
142
+ */
143
+ function isInvisibleText(controlNode) {
144
+ return controlNode.type === 'sap-ui-control' &&
145
+ controlNode.name && controlNode.name.indexOf('InvisibleText') !== -1;
146
+ }
147
+
148
+ /**
149
+ * Check if any key or value in an object matches the given matcher function.
150
+ * @param {Object} oObject - Object to check
151
+ * @param {Function} fnMatcher - Function that returns true if a value matches
152
+ * @returns {boolean} True if any key or value matches
153
+ * @private
154
+ */
155
+ function matchesObjectKeysOrValues(oObject, fnMatcher) {
156
+ if (!oObject || typeof oObject !== 'object') {
157
+ return false;
158
+ }
159
+ var aKeys = Object.keys(oObject);
160
+ for (var i = 0; i < aKeys.length; i++) {
161
+ var sKey = aKeys[i];
162
+ if (fnMatcher(sKey)) {
163
+ return true;
164
+ }
165
+ var vValue = oObject[sKey];
166
+ if (vValue !== null && vValue !== undefined) {
167
+ if (fnMatcher(vValue)) {
168
+ return true;
169
+ }
170
+ }
171
+ }
172
+ return false;
173
+ }
174
+
175
+ return filterControlTree;
176
+ });