@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 +0 -51
- package/THIRDPARTY.txt +6 -50
- package/package.json +6 -6
- package/src/sap/ui/testrecorder/.library +1 -1
- package/src/sap/ui/testrecorder/ControlTree.js +303 -0
- package/src/sap/ui/testrecorder/codeSnippets/CodeSnippetProvider.js +1 -0
- package/src/sap/ui/testrecorder/codeSnippets/ControlSnippetGenerator.js +1 -0
- package/src/sap/ui/testrecorder/codeSnippets/OPA5ControlSnippetGenerator.js +34 -10
- package/src/sap/ui/testrecorder/inspector/ControlAPI.js +70 -3
- package/src/sap/ui/testrecorder/inspector/ControlInspector.js +49 -16
- package/src/sap/ui/testrecorder/library.js +2 -2
- package/src/sap/ui/testrecorder/ui/views/Main.view.xml +13 -13
- package/src/sap/ui/testrecorder/utils/convertTreeToMarkdown.js +150 -0
- package/src/sap/ui/testrecorder/utils/filterControlTree.js +176 -0
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.
|
|
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
|
+
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
18
|
-
"@openui5/sap.ui.codeeditor": "1.
|
|
19
|
-
"@openui5/sap.ui.core": "1.
|
|
20
|
-
"@openui5/sap.ui.layout": "1.
|
|
21
|
-
"@openui5/sap.ui.support": "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.
|
|
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
|
|
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
|
-
|
|
49
|
+
var sReturnPrefix = mData.settings && mData.settings.preferChainableSnippets ? "return " : "";
|
|
50
|
+
return sReturnPrefix + "this.waitFor(" + sFullSelector + ");";
|
|
45
51
|
};
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
53
|
-
|
|
63
|
+
return "new Press(" + this._getSettingsAsString(oSettings, 2) + ")";
|
|
64
|
+
|
|
54
65
|
case Commands.ENTER_TEXT:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
*
|
|
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.
|
|
103
|
-
var
|
|
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
|
|
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:
|
|
116
|
-
|
|
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
|
-
})
|
|
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:
|
|
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.
|
|
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.
|
|
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
|
+
});
|