@sapui5/sap.fe.test 1.103.0 → 1.104.1
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/package.json +3 -4
- package/src/sap/fe/test/.library +1 -1
- package/src/sap/fe/test/BaseActions.js +17 -0
- package/src/sap/fe/test/JestTemplatingHelper.js +94 -3
- package/src/sap/fe/test/JestTemplatingHelper.ts +76 -1
- package/src/sap/fe/test/ListReport.js +6 -6
- package/src/sap/fe/test/ObjectPage.js +9 -1
- package/src/sap/fe/test/UI5MockHelper.js +13 -6
- package/src/sap/fe/test/UI5MockHelper.ts +13 -2
- package/src/sap/fe/test/api/DialogAssertions.js +1 -1
- package/src/sap/fe/test/api/DialogValueHelpAssertions.js +36 -0
- package/src/sap/fe/test/builder/MdcFilterBarBuilder.js +102 -104
- package/src/sap/fe/test/builder/VMBuilder.js +13 -13
- package/src/sap/fe/test/internal/ConsoleErrorChecker.js +241 -0
- package/src/sap/fe/test/internal/ConsoleErrorChecker.ts +206 -0
- package/src/sap/fe/test/internal/FEArrangements.js +28 -8
- package/src/sap/fe/test/library.js +1 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* SAP UI development toolkit for HTML5 (SAPUI5)
|
|
3
|
+
* (c) Copyright 2009-2021 SAP SE. All rights reserved
|
|
4
|
+
*/
|
|
5
|
+
sap.ui.define([], function () {
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
function wrapPatterns(pattern) {
|
|
9
|
+
if (pattern instanceof RegExp) {
|
|
10
|
+
return function (message) {
|
|
11
|
+
return message.match(pattern) !== null;
|
|
12
|
+
};
|
|
13
|
+
} else {
|
|
14
|
+
return function (message) {
|
|
15
|
+
return message.includes(pattern);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* List of error message patterns that are always accepted.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
var GLOBALLY_ACCEPTED_ERRORS = ["failed to load JavaScript resource: sap/esh/search/ui/i18n.js" // shell
|
|
25
|
+
].map(wrapPatterns);
|
|
26
|
+
|
|
27
|
+
var ConsoleErrorChecker = /*#__PURE__*/function () {
|
|
28
|
+
function ConsoleErrorChecker(window) {
|
|
29
|
+
var _this = this;
|
|
30
|
+
|
|
31
|
+
this.matchers = [];
|
|
32
|
+
this.messages = [];
|
|
33
|
+
this.observer = new MutationObserver(function (mutations) {
|
|
34
|
+
var opaFrame = mutations.reduce(function (iFrame, mutation) {
|
|
35
|
+
if (iFrame !== null) {
|
|
36
|
+
return iFrame;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (var _i = 0, _Array$from = Array.from(mutation.addedNodes); _i < _Array$from.length; _i++) {
|
|
40
|
+
var node = _Array$from[_i];
|
|
41
|
+
|
|
42
|
+
if (node instanceof Element) {
|
|
43
|
+
var element = node.querySelector("#OpaFrame");
|
|
44
|
+
|
|
45
|
+
if (element instanceof HTMLIFrameElement && element.contentWindow) {
|
|
46
|
+
return element;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return iFrame;
|
|
52
|
+
}, null);
|
|
53
|
+
|
|
54
|
+
if (opaFrame && opaFrame.contentWindow) {
|
|
55
|
+
_this.prepareWindow(opaFrame.contentWindow);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
QUnit.moduleStart(function () {
|
|
59
|
+
_this.observer.observe(window.document.body, {
|
|
60
|
+
childList: true
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
QUnit.moduleDone(function () {
|
|
64
|
+
_this.observer.disconnect();
|
|
65
|
+
});
|
|
66
|
+
QUnit.testStart(function () {
|
|
67
|
+
_this.reset();
|
|
68
|
+
});
|
|
69
|
+
QUnit.log(function () {
|
|
70
|
+
_this.handleFailedMessages();
|
|
71
|
+
});
|
|
72
|
+
this.karma = window.__karma__; // either go for Karma config option "ui5.config.strictConsoleErrors" or use URL query parameter "strict"
|
|
73
|
+
|
|
74
|
+
var search = new URLSearchParams(window.location.search);
|
|
75
|
+
var urlParam = search.get("strictConsoleErrors");
|
|
76
|
+
|
|
77
|
+
if (urlParam !== null) {
|
|
78
|
+
this.isStrict = urlParam === "true";
|
|
79
|
+
} else {
|
|
80
|
+
var _this$karma$config$ui, _this$karma, _this$karma$config$ui2;
|
|
81
|
+
|
|
82
|
+
this.isStrict = (_this$karma$config$ui = (_this$karma = this.karma) === null || _this$karma === void 0 ? void 0 : (_this$karma$config$ui2 = _this$karma.config.ui5) === null || _this$karma$config$ui2 === void 0 ? void 0 : _this$karma$config$ui2.config.strictconsoleerrors) !== null && _this$karma$config$ui !== void 0 ? _this$karma$config$ui : false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.reset();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
var _proto = ConsoleErrorChecker.prototype;
|
|
89
|
+
|
|
90
|
+
_proto.handleFailedMessages = function handleFailedMessages() {
|
|
91
|
+
var failedMessages = this.messages;
|
|
92
|
+
this.messages = [];
|
|
93
|
+
|
|
94
|
+
if (failedMessages.length > 0) {
|
|
95
|
+
QUnit.assert.pushResult({
|
|
96
|
+
result: false,
|
|
97
|
+
source: "FE Console Log Check",
|
|
98
|
+
message: "There were ".concat(failedMessages.length, " unexpected console errors"),
|
|
99
|
+
actual: failedMessages,
|
|
100
|
+
expected: []
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
_proto.reset = function reset() {
|
|
106
|
+
this.messages = []; // this sets the default to apply if no allowed patterns are set via setAcceptedErrorPatterns().
|
|
107
|
+
|
|
108
|
+
if (this.isStrict) {
|
|
109
|
+
this.matchers = GLOBALLY_ACCEPTED_ERRORS;
|
|
110
|
+
} else {
|
|
111
|
+
this.matchers = [function () {
|
|
112
|
+
return true;
|
|
113
|
+
}];
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
_proto.setAcceptedErrorPatterns = function setAcceptedErrorPatterns(patterns) {
|
|
118
|
+
if (!patterns || patterns.length === 0) {
|
|
119
|
+
this.matchers = GLOBALLY_ACCEPTED_ERRORS;
|
|
120
|
+
} else {
|
|
121
|
+
this.matchers = patterns.map(wrapPatterns).concat(GLOBALLY_ACCEPTED_ERRORS);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
_proto.checkAndLog = function checkAndLog(type) {
|
|
126
|
+
for (var _len = arguments.length, data = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
|
127
|
+
data[_key - 1] = arguments[_key];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// only check the error messages
|
|
131
|
+
if (type === "error") {
|
|
132
|
+
var messageText = data[0];
|
|
133
|
+
var isAllowed = this.matchers.some(function (matcher) {
|
|
134
|
+
return matcher(messageText);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (!isAllowed) {
|
|
138
|
+
this.messages.push(messageText);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (this.karma) {
|
|
143
|
+
// wrap the data to facilitate parsing in the backend
|
|
144
|
+
var wrappedData = data.map(function (d) {
|
|
145
|
+
return [d];
|
|
146
|
+
});
|
|
147
|
+
this.karma.log(type, wrappedData);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
_proto.prepareWindow = function prepareWindow(window) {
|
|
152
|
+
var _this2 = this;
|
|
153
|
+
|
|
154
|
+
var console = window.console; // capture console.log(), console.debug(), etc.
|
|
155
|
+
|
|
156
|
+
var patchConsoleMethod = function (method) {
|
|
157
|
+
var fnOriginal = console[method];
|
|
158
|
+
|
|
159
|
+
console[method] = function () {
|
|
160
|
+
for (var _len2 = arguments.length, data = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
|
|
161
|
+
data[_key2] = arguments[_key2];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_this2.checkAndLog.apply(_this2, [method].concat(data));
|
|
165
|
+
|
|
166
|
+
return fnOriginal.apply(console, data);
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
patchConsoleMethod("log");
|
|
171
|
+
patchConsoleMethod("debug");
|
|
172
|
+
patchConsoleMethod("info");
|
|
173
|
+
patchConsoleMethod("warn");
|
|
174
|
+
patchConsoleMethod("error"); // capture console.assert()
|
|
175
|
+
// see https://console.spec.whatwg.org/#assert
|
|
176
|
+
|
|
177
|
+
console.assert = function () {
|
|
178
|
+
var condition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
|
|
179
|
+
|
|
180
|
+
if (condition) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
var message = "Assertion failed";
|
|
185
|
+
|
|
186
|
+
for (var _len3 = arguments.length, data = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
|
|
187
|
+
data[_key3 - 1] = arguments[_key3];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (data.length === 0) {
|
|
191
|
+
data.push(message);
|
|
192
|
+
} else {
|
|
193
|
+
var first = data[0];
|
|
194
|
+
|
|
195
|
+
if (typeof first !== "string") {
|
|
196
|
+
data.unshift(message);
|
|
197
|
+
} else {
|
|
198
|
+
first = "".concat(message, ": ").concat(first);
|
|
199
|
+
data[0] = first;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.error.apply(console, data);
|
|
204
|
+
}; // capture errors
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
function onPromiseRejection(event) {
|
|
208
|
+
var _event$reason;
|
|
209
|
+
|
|
210
|
+
var message = "UNHANDLED PROMISE REJECTION: ".concat(event.reason);
|
|
211
|
+
this.checkAndLog("error", message, (_event$reason = event.reason) === null || _event$reason === void 0 ? void 0 : _event$reason.stack);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function onError(event) {
|
|
215
|
+
var message = event.message;
|
|
216
|
+
this.checkAndLog("error", message, event.filename);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
window.addEventListener("error", onError.bind(this), {
|
|
220
|
+
passive: true
|
|
221
|
+
});
|
|
222
|
+
window.addEventListener("unhandledrejection", onPromiseRejection.bind(this), {
|
|
223
|
+
passive: true
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
ConsoleErrorChecker.getInstance = function getInstance(window) {
|
|
228
|
+
// the global instance is needed to support multiple tests in a row (in Karma)
|
|
229
|
+
if (!window.sapFEConsoleErrorChecker) {
|
|
230
|
+
window.sapFEConsoleErrorChecker = new ConsoleErrorChecker(window);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return window.sapFEConsoleErrorChecker;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
return ConsoleErrorChecker;
|
|
237
|
+
}();
|
|
238
|
+
|
|
239
|
+
return ConsoleErrorChecker.getInstance(window);
|
|
240
|
+
}, false);
|
|
241
|
+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkNvbnNvbGVFcnJvckNoZWNrZXIudHMiXSwibmFtZXMiOlsid3JhcFBhdHRlcm5zIiwicGF0dGVybiIsIlJlZ0V4cCIsIm1lc3NhZ2UiLCJtYXRjaCIsImluY2x1ZGVzIiwiR0xPQkFMTFlfQUNDRVBURURfRVJST1JTIiwibWFwIiwiQ29uc29sZUVycm9yQ2hlY2tlciIsIndpbmRvdyIsIm1hdGNoZXJzIiwibWVzc2FnZXMiLCJvYnNlcnZlciIsIk11dGF0aW9uT2JzZXJ2ZXIiLCJtdXRhdGlvbnMiLCJvcGFGcmFtZSIsInJlZHVjZSIsImlGcmFtZSIsIm11dGF0aW9uIiwiQXJyYXkiLCJmcm9tIiwiYWRkZWROb2RlcyIsIm5vZGUiLCJFbGVtZW50IiwiZWxlbWVudCIsInF1ZXJ5U2VsZWN0b3IiLCJIVE1MSUZyYW1lRWxlbWVudCIsImNvbnRlbnRXaW5kb3ciLCJwcmVwYXJlV2luZG93IiwiUVVuaXQiLCJtb2R1bGVTdGFydCIsIm9ic2VydmUiLCJkb2N1bWVudCIsImJvZHkiLCJjaGlsZExpc3QiLCJtb2R1bGVEb25lIiwiZGlzY29ubmVjdCIsInRlc3RTdGFydCIsInJlc2V0IiwibG9nIiwiaGFuZGxlRmFpbGVkTWVzc2FnZXMiLCJrYXJtYSIsIl9fa2FybWFfXyIsInNlYXJjaCIsIlVSTFNlYXJjaFBhcmFtcyIsImxvY2F0aW9uIiwidXJsUGFyYW0iLCJnZXQiLCJpc1N0cmljdCIsImNvbmZpZyIsInVpNSIsInN0cmljdGNvbnNvbGVlcnJvcnMiLCJmYWlsZWRNZXNzYWdlcyIsImxlbmd0aCIsImFzc2VydCIsInB1c2hSZXN1bHQiLCJyZXN1bHQiLCJzb3VyY2UiLCJhY3R1YWwiLCJleHBlY3RlZCIsInNldEFjY2VwdGVkRXJyb3JQYXR0ZXJucyIsInBhdHRlcm5zIiwiY29uY2F0IiwiY2hlY2tBbmRMb2ciLCJ0eXBlIiwiZGF0YSIsIm1lc3NhZ2VUZXh0IiwiaXNBbGxvd2VkIiwic29tZSIsIm1hdGNoZXIiLCJwdXNoIiwid3JhcHBlZERhdGEiLCJkIiwiY29uc29sZSIsInBhdGNoQ29uc29sZU1ldGhvZCIsIm1ldGhvZCIsImZuT3JpZ2luYWwiLCJhcHBseSIsImNvbmRpdGlvbiIsImZpcnN0IiwidW5zaGlmdCIsImVycm9yIiwib25Qcm9taXNlUmVqZWN0aW9uIiwiZXZlbnQiLCJyZWFzb24iLCJzdGFjayIsIm9uRXJyb3IiLCJmaWxlbmFtZSIsImFkZEV2ZW50TGlzdGVuZXIiLCJiaW5kIiwicGFzc2l2ZSIsImdldEluc3RhbmNlIiwic2FwRkVDb25zb2xlRXJyb3JDaGVja2VyIl0sIm1hcHBpbmdzIjoiO0FBQUE7QUFDQTtBQUNBOzs7O0FBWUEsV0FBU0EsWUFBVCxDQUFzQkMsT0FBdEIsRUFBd0U7QUFDdkUsUUFBSUEsT0FBTyxZQUFZQyxNQUF2QixFQUErQjtBQUM5QixhQUFPLFVBQUNDLE9BQUQ7QUFBQSxlQUFhQSxPQUFPLENBQUNDLEtBQVIsQ0FBY0gsT0FBZCxNQUEyQixJQUF4QztBQUFBLE9BQVA7QUFDQSxLQUZELE1BRU87QUFDTixhQUFPLFVBQUNFLE9BQUQ7QUFBQSxlQUFhQSxPQUFPLENBQUNFLFFBQVIsQ0FBaUJKLE9BQWpCLENBQWI7QUFBQSxPQUFQO0FBQ0E7QUFDRDtBQUVEO0FBQ0E7QUFDQTs7O0FBQ0EsTUFBTUssd0JBQXdCLEdBQUcsQ0FDaEMsK0RBRGdDLENBQ2dDO0FBRGhDLElBRS9CQyxHQUYrQixDQUUzQlAsWUFGMkIsQ0FBakM7O01BSU1RLG1CO0FBNkJMLGlDQUFZQyxNQUFaLEVBQW9EO0FBQUE7O0FBQUEsV0E1QjVDQyxRQTRCNEMsR0E1QlAsRUE0Qk87QUFBQSxXQTNCNUNDLFFBMkI0QyxHQTNCdkIsRUEyQnVCO0FBQUEsV0F2Qm5DQyxRQXVCbUMsR0F2QnhCLElBQUlDLGdCQUFKLENBQXFCLFVBQUNDLFNBQUQsRUFBZTtBQUMvRCxZQUFNQyxRQUFRLEdBQUdELFNBQVMsQ0FBQ0UsTUFBVixDQUFpQixVQUFDQyxNQUFELEVBQW1DQyxRQUFuQyxFQUFnRTtBQUNqRyxjQUFJRCxNQUFNLEtBQUssSUFBZixFQUFx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QW1CbEYsTUFBbkIsRUFBNkc7QUFDNUc7QUFDQSxVQUFJLENBQUNBLE1BQU0sQ0FBQ21GLHdCQUFaLEVBQXNDO0FBQ3JDbkYsUUFBQUEsTUFBTSxDQUFDbUYsd0JBQVAsR0FBa0MsSUFBSXBGLG1CQUFKLENBQXdCQyxNQUF4QixDQUFsQztBQUNBOztBQUNELGFBQU9BLE1BQU0sQ0FBQ21GLHdCQUFkO0FBQ0EsSzs7Ozs7U0FHYXBGLG1CQUFtQixDQUFDbUYsV0FBcEIsQ0FBZ0NsRixNQUFoQyxDIiwic291cmNlUm9vdCI6Ii4iLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgdHlwZSB7IEJyb3dzZXJDb25zb2xlTG9nT3B0aW9ucyB9IGZyb20gXCJrYXJtYVwiO1xuXG50eXBlIE1lc3NhZ2VNYXRjaGVyRnVuY3Rpb24gPSAobWVzc2FnZTogc3RyaW5nKSA9PiBib29sZWFuO1xudHlwZSBLYXJtYSA9IHtcblx0bG9nOiAobGV2ZWw6IEJyb3dzZXJDb25zb2xlTG9nT3B0aW9uc1tcImxldmVsXCJdLCAuLi5kYXRhOiBhbnlbXSkgPT4gdm9pZDtcblx0Y29uZmlnOiB7XG5cdFx0dWk1Pzoge1xuXHRcdFx0Y29uZmlnOiB7XG5cdFx0XHRcdHN0cmljdGNvbnNvbGVlcnJvcnM/OiBib29sZWFuOyAvLyBLYXJtYSBvcHRpb25zIGFyZSBhbGwgbG93ZXJjYXNlIGF0IHJ1bnRpbWUhXG5cdFx0XHR9O1xuXHRcdH07XG5cdH07XG59O1xuXG5mdW5jdGlvbiB3cmFwUGF0dGVybnMocGF0dGVybjogUmVnRXhwIHwgc3RyaW5nKTogTWVzc2FnZU1hdGNoZXJGdW5jdGlvbiB7XG5cdGlmIChwYXR0ZXJuIGluc3RhbmNlb2YgUmVnRXhwKSB7XG5cdFx0cmV0dXJuIChtZXNzYWdlKSA9PiBtZXNzYWdlLm1hdGNoKHBhdHRlcm4pICE9PSBudWxsO1xuXHR9IGVsc2Uge1xuXHRcdHJldHVybiAobWVzc2FnZSkgPT4gbWVzc2FnZS5pbmNsdWRlcyhwYXR0ZXJuKTtcblx0fVxufVxuXG4vKipcbiAqIExpc3Qgb2YgZXJyb3IgbWVzc2FnZSBwYXR0ZXJucyB0aGF0IGFyZSBhbHdheXMgYWNjZXB0ZWQuXG4gKi9cbmNvbnN0IEdMT0JBTExZX0FDQ0VQVEVEX0VSUk9SUyA9IFtcblx0XCJmYWlsZWQgdG8gbG9hZCBKYXZhU2NyaXB0IHJlc291cmNlOiBzYXAvZXNoL3NlYXJjaC91aS9pMThuLmpzXCIgLy8gc2hlbGxcbl0ubWFwKHdyYXBQYXR0ZXJucyk7XG5cbmNsYXNzIENvbnNvbGVFcnJvckNoZWNrZXIge1xuXHRwcml2YXRlIG1hdGNoZXJzOiBNZXNzYWdlTWF0Y2hlckZ1bmN0aW9uW10gPSBbXTtcblx0cHJpdmF0ZSBtZXNzYWdlczogc3RyaW5nW10gPSBbXTtcblx0cHJpdmF0ZSByZWFkb25seSBrYXJtYTogS2FybWEgfCB1bmRlZmluZWQ7XG5cdHByaXZhdGUgcmVhZG9ubHkgaXNTdHJpY3Q6IGJvb2xlYW47XG5cblx0cHJpdmF0ZSByZWFkb25seSBvYnNlcnZlciA9IG5ldyBNdXRhdGlvbk9ic2VydmVyKChtdXRhdGlvbnMpID0+IHtcblx0XHRjb25zdCBvcGFGcmFtZSA9IG11dGF0aW9ucy5yZWR1Y2UoKGlGcmFtZTogSFRNTElGcmFtZUVsZW1lbnQgfCBudWxsLCBtdXRhdGlvbjogTXV0YXRpb25SZWNvcmQpID0+IHtcblx0XHRcdGlmIChpRnJhbWUgIT09IG51bGwpIHtcblx0XHRcdFx0cmV0dXJuIGlGcmFtZTtcblx0XHRcdH1cblxuXHRcdFx0Zm9yIChjb25zdCBub2RlIG9mIEFycmF5LmZyb20obXV0YXRpb24uYWRkZWROb2RlcykpIHtcblx0XHRcdFx0aWYgKG5vZGUgaW5zdGFuY2VvZiBFbGVtZW50KSB7XG5cdFx0XHRcdFx0Y29uc3QgZWxlbWVudCA9IG5vZGUucXVlcnlTZWxlY3RvcihcIiNPcGFGcmFtZVwiKTtcblx0XHRcdFx0XHRpZiAoZWxlbWVudCBpbnN0YW5jZW9mIEhUTUxJRnJhbWVFbGVtZW50ICYmIGVsZW1lbnQuY29udGVudFdpbmRvdykge1xuXHRcdFx0XHRcdFx0cmV0dXJuIGVsZW1lbnQ7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9XG5cdFx0XHR9XG5cblx0XHRcdHJldHVybiBpRnJhbWU7XG5cdFx0fSwgbnVsbCk7XG5cblx0XHRpZiAob3BhRnJhbWUgJiYgb3BhRnJhbWUuY29udGVudFdpbmRvdykge1xuXHRcdFx0dGhpcy5wcmVwYXJlV2luZG93KG9wYUZyYW1lLmNvbnRlbnRXaW5kb3cpO1xuXHRcdH1cblx0fSk7XG5cblx0Y29uc3RydWN0b3Iod2luZG93OiBXaW5kb3cgJiB7IF9fa2FybWFfXz86IEthcm1hIH0pIHtcblx0XHRRVW5pdC5tb2R1bGVTdGFydCgoKSA9PiB7XG5cdFx0XHR0aGlzLm9ic2VydmVyLm9ic2VydmUod2luZG93LmRvY3VtZW50LmJvZHksIHsgY2hpbGRMaXN0OiB0cnVlIH0pO1xuXHRcdH0pO1xuXG5cdFx0UVVuaXQubW9kdWxlRG9uZSgoKSA9PiB7XG5cdFx0XHR0aGlzLm9ic2VydmVyLmRpc2Nvbm5lY3QoKTtcblx0XHR9KTtcblxuXHRcdFFVbml0LnRlc3RTdGFydCgoKSA9PiB7XG5cdFx0XHR0aGlzLnJlc2V0KCk7XG5cdFx0fSk7XG5cblx0XHRRVW5pdC5sb2coKCkgPT4ge1xuXHRcdFx0dGhpcy5oYW5kbGVGYWlsZWRNZXNzYWdlcygpO1xuXHRcdH0pO1xuXG5cdFx0dGhpcy5rYXJtYSA9IHdpbmRvdy5fX2thcm1hX187XG5cblx0XHQvLyBlaXRoZXIgZ28gZm9yIEthcm1hIGNvbmZpZyBvcHRpb24gXCJ1aTUuY29uZmlnLnN0cmljdENvbnNvbGVFcnJvcnNcIiBvciB1c2UgVVJMIHF1ZXJ5IHBhcmFtZXRlciBcInN0cmljdFwiXG5cdFx0Y29uc3Qgc2VhcmNoID0gbmV3IFVSTFNlYXJjaFBhcmFtcyh3aW5kb3cubG9jYXRpb24uc2VhcmNoKTtcblx0XHRjb25zdCB1cmxQYXJhbSA9IHNlYXJjaC5nZXQoXCJzdHJpY3RDb25zb2xlRXJyb3JzXCIpO1xuXHRcdGlmICh1cmxQYXJhbSAhPT0gbnVsbCkge1xuXHRcdFx0dGhpcy5pc1N0cmljdCA9IHVybFBhcmFtID09PSBcInRydWVcIjtcblx0XHR9IGVsc2Uge1xuXHRcdFx0dGhpcy5pc1N0cmljdCA9IHRoaXMua2FybWE/LmNvbmZpZy51aTU/LmNvbmZpZy5zdHJpY3Rjb25zb2xlZXJyb3JzID8/IGZhbHNlO1xuXHRcdH1cblxuXHRcdHRoaXMucmVzZXQoKTtcblx0fVxuXG5cdHByaXZhdGUgaGFuZGxlRmFpbGVkTWVzc2FnZXMoKSB7XG5cdFx0Y29uc3QgZmFpbGVkTWVzc2FnZXMgPSB0aGlzLm1lc3NhZ2VzO1xuXHRcdHRoaXMubWVzc2FnZXMgPSBbXTtcblxuXHRcdGlmIChmYWlsZWRNZXNzYWdlcy5sZW5ndGggPiAwKSB7XG5cdFx0XHRRVW5pdC5hc3NlcnQucHVzaFJlc3VsdCh7XG5cdFx0XHRcdHJlc3VsdDogZmFsc2UsXG5cdFx0XHRcdHNvdXJjZTogXCJGRSBDb25zb2xlIExvZyBDaGVja1wiLFxuXHRcdFx0XHRtZXNzYWdlOiBgVGhlcmUgd2VyZSAke2ZhaWxlZE1lc3NhZ2VzLmxlbmd0aH0gdW5leHBlY3RlZCBjb25zb2xlIGVycm9yc2AsXG5cdFx0XHRcdGFjdHVhbDogZmFpbGVkTWVzc2FnZXMsXG5cdFx0XHRcdGV4cGVjdGVkOiBbXVxuXHRcdFx0fSk7XG5cdFx0fVxuXHR9XG5cblx0cHJpdmF0ZSByZXNldCgpIHtcblx0XHR0aGlzLm1lc3NhZ2VzID0gW107XG5cblx0XHQvLyB0aGlzIHNldHMgdGhlIGRlZmF1bHQgdG8gYXBwbHkgaWYgbm8gYWxsb3dlZCBwYXR0ZXJucyBhcmUgc2V0IHZpYSBzZXRBY2NlcHRlZEVycm9yUGF0dGVybnMoKS5cblx0XHRpZiAodGhpcy5pc1N0cmljdCkge1xuXHRcdFx0dGhpcy5tYXRjaGVycyA9IEdMT0JBTExZX0FDQ0VQVEVEX0VSUk9SUztcblx0XHR9IGVsc2Uge1xuXHRcdFx0dGhpcy5tYXRjaGVycyA9IFsoKSA9PiB0cnVlXTtcblx0XHR9XG5cdH1cblxuXHRzZXRBY2NlcHRlZEVycm9yUGF0dGVybnMocGF0dGVybnM/OiAoUmVnRXhwIHwgc3RyaW5nKVtdKSB7XG5cdFx0aWYgKCFwYXR0ZXJucyB8fCBwYXR0ZXJucy5sZW5ndGggPT09IDApIHtcblx0XHRcdHRoaXMubWF0Y2hlcnMgPSBHTE9CQUxMWV9BQ0NFUFRFRF9FUlJPUlM7XG5cdFx0fSBlbHNlIHtcblx0XHRcdHRoaXMubWF0Y2hlcnMgPSBwYXR0ZXJucy5tYXAod3JhcFBhdHRlcm5zKS5jb25jYXQoR0xPQkFMTFlfQUNDRVBURURfRVJST1JTKTtcblx0XHR9XG5cdH1cblxuXHRwcml2YXRlIGNoZWNrQW5kTG9nKHR5cGU6IEJyb3dzZXJDb25zb2xlTG9nT3B0aW9uc1tcImxldmVsXCJdLCAuLi5kYXRhOiBhbnlbXSkge1xuXHRcdC8vIG9ubHkgY2hlY2sgdGhlIGVycm9yIG1lc3NhZ2VzXG5cdFx0aWYgKHR5cGUgPT09IFwiZXJyb3JcIikge1xuXHRcdFx0Y29uc3QgbWVzc2FnZVRleHQgPSBkYXRhWzBdO1xuXHRcdFx0Y29uc3QgaXNBbGxvd2VkID0gdGhpcy5tYXRjaGVycy5zb21lKChtYXRjaGVyKSA9PiBtYXRjaGVyKG1lc3NhZ2VUZXh0KSk7XG5cdFx0XHRpZiAoIWlzQWxsb3dlZCkge1xuXHRcdFx0XHR0aGlzLm1lc3NhZ2VzLnB1c2gobWVzc2FnZVRleHQpO1xuXHRcdFx0fVxuXHRcdH1cblxuXHRcdGlmICh0aGlzLmthcm1hKSB7XG5cdFx0XHQvLyB3cmFwIHRoZSBkYXRhIHRvIGZhY2lsaXRhdGUgcGFyc2luZyBpbiB0aGUgYmFja2VuZFxuXHRcdFx0Y29uc3Qgd3JhcHBlZERhdGEgPSBkYXRhLm1hcCgoZCkgPT4gW2RdKTtcblx0XHRcdHRoaXMua2FybWEubG9nKHR5cGUsIHdyYXBwZWREYXRhKTtcblx0XHR9XG5cdH1cblxuXHRwcml2YXRlIHByZXBhcmVXaW5kb3cod2luZG93OiBXaW5kb3cpIHtcblx0XHRjb25zdCBjb25zb2xlOiBDb25zb2xlID0gKHdpbmRvdyBhcyBhbnkpLmNvbnNvbGU7XG5cblx0XHQvLyBjYXB0dXJlIGNvbnNvbGUubG9nKCksIGNvbnNvbGUuZGVidWcoKSwgZXRjLlxuXHRcdGNvbnN0IHBhdGNoQ29uc29sZU1ldGhvZCA9IChtZXRob2Q6IFwibG9nXCIgfCBcImluZm9cIiB8IFwid2FyblwiIHwgXCJlcnJvclwiIHwgXCJkZWJ1Z1wiKSA9PiB7XG5cdFx0XHRjb25zdCBmbk9yaWdpbmFsID0gY29uc29sZVttZXRob2RdO1xuXHRcdFx0Y29uc29sZVttZXRob2RdID0gKC4uLmRhdGE6IGFueVtdKTogdm9pZCA9PiB7XG5cdFx0XHRcdHRoaXMuY2hlY2tBbmRMb2cobWV0aG9kLCAuLi5kYXRhKTtcblx0XHRcdFx0cmV0dXJuIGZuT3JpZ2luYWwuYXBwbHkoY29uc29sZSwgZGF0YSk7XG5cdFx0XHR9O1xuXHRcdH07XG5cblx0XHRwYXRjaENvbnNvbGVNZXRob2QoXCJsb2dcIik7XG5cdFx0cGF0Y2hDb25zb2xlTWV0aG9kKFwiZGVidWdcIik7XG5cdFx0cGF0Y2hDb25zb2xlTWV0aG9kKFwiaW5mb1wiKTtcblx0XHRwYXRjaENvbnNvbGVNZXRob2QoXCJ3YXJuXCIpO1xuXHRcdHBhdGNoQ29uc29sZU1ldGhvZChcImVycm9yXCIpO1xuXG5cdFx0Ly8gY2FwdHVyZSBjb25zb2xlLmFzc2VydCgpXG5cdFx0Ly8gc2VlIGh0dHBzOi8vY29uc29sZS5zcGVjLndoYXR3Zy5vcmcvI2Fzc2VydFxuXHRcdGNvbnNvbGUuYXNzZXJ0ID0gZnVuY3Rpb24gKGNvbmRpdGlvbiA9IGZhbHNlLCAuLi5kYXRhOiBhbnlbXSkge1xuXHRcdFx0aWYgKGNvbmRpdGlvbikge1xuXHRcdFx0XHRyZXR1cm47XG5cdFx0XHR9XG5cblx0XHRcdGNvbnN0IG1lc3NhZ2UgPSBcIkFzc2VydGlvbiBmYWlsZWRcIjtcblx0XHRcdGlmIChkYXRhLmxlbmd0aCA9PT0gMCkge1xuXHRcdFx0XHRkYXRhLnB1c2gobWVzc2FnZSk7XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRsZXQgZmlyc3QgPSBkYXRhWzBdO1xuXHRcdFx0XHRpZiAodHlwZW9mIGZpcnN0ICE9PSBcInN0cmluZ1wiKSB7XG5cdFx0XHRcdFx0ZGF0YS51bnNoaWZ0KG1lc3NhZ2UpO1xuXHRcdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRcdGZpcnN0ID0gYCR7bWVzc2FnZX06ICR7Zmlyc3R9YDtcblx0XHRcdFx0XHRkYXRhWzBdID0gZmlyc3Q7XG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0Y29uc29sZS5lcnJvciguLi5kYXRhKTtcblx0XHR9O1xuXG5cdFx0Ly8gY2FwdHVyZSBlcnJvcnNcblx0XHRmdW5jdGlvbiBvblByb21pc2VSZWplY3Rpb24odGhpczogQ29uc29sZUVycm9yQ2hlY2tlciwgZXZlbnQ6IFByb21pc2VSZWplY3Rpb25FdmVudCkge1xuXHRcdFx0Y29uc3QgbWVzc2FnZSA9IGBVTkhBTkRMRUQgUFJPTUlTRSBSRUpFQ1RJT046ICR7ZXZlbnQucmVhc29ufWA7XG5cdFx0XHR0aGlzLmNoZWNrQW5kTG9nKFwiZXJyb3JcIiwgbWVzc2FnZSwgZXZlbnQucmVhc29uPy5zdGFjayk7XG5cdFx0fVxuXG5cdFx0ZnVuY3Rpb24gb25FcnJvcih0aGlzOiBDb25zb2xlRXJyb3JDaGVja2VyLCBldmVudDogRXJyb3JFdmVudCkge1xuXHRcdFx0Y29uc3QgbWVzc2FnZSA9IGV2ZW50Lm1lc3NhZ2U7XG5cdFx0XHR0aGlzLmNoZWNrQW5kTG9nKFwiZXJyb3JcIiwgbWVzc2FnZSwgZXZlbnQuZmlsZW5hbWUpO1xuXHRcdH1cblxuXHRcdHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKFwiZXJyb3JcIiwgb25FcnJvci5iaW5kKHRoaXMpLCB7IHBhc3NpdmU6IHRydWUgfSk7XG5cdFx0d2luZG93LmFkZEV2ZW50TGlzdGVuZXIoXCJ1bmhhbmRsZWRyZWplY3Rpb25cIiwgb25Qcm9taXNlUmVqZWN0aW9uLmJpbmQodGhpcyksIHsgcGFzc2l2ZTogdHJ1ZSB9KTtcblx0fVxuXG5cdHN0YXRpYyBnZXRJbnN0YW5jZSh3aW5kb3c6IFdpbmRvdyAmIHsgc2FwRkVDb25zb2xlRXJyb3JDaGVja2VyPzogQ29uc29sZUVycm9yQ2hlY2tlciB9KTogQ29uc29sZUVycm9yQ2hlY2tlciB7XG5cdFx0Ly8gdGhlIGdsb2JhbCBpbnN0YW5jZSBpcyBuZWVkZWQgdG8gc3VwcG9ydCBtdWx0aXBsZSB0ZXN0cyBpbiBhIHJvdyAoaW4gS2FybWEpXG5cdFx0aWYgKCF3aW5kb3cuc2FwRkVDb25zb2xlRXJyb3JDaGVja2VyKSB7XG5cdFx0XHR3aW5kb3cuc2FwRkVDb25zb2xlRXJyb3JDaGVja2VyID0gbmV3IENvbnNvbGVFcnJvckNoZWNrZXIod2luZG93KTtcblx0XHR9XG5cdFx0cmV0dXJuIHdpbmRvdy5zYXBGRUNvbnNvbGVFcnJvckNoZWNrZXI7XG5cdH1cbn1cblxuZXhwb3J0IGRlZmF1bHQgQ29uc29sZUVycm9yQ2hlY2tlci5nZXRJbnN0YW5jZSh3aW5kb3cpO1xuIl19
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { BrowserConsoleLogOptions } from "karma";
|
|
2
|
+
|
|
3
|
+
type MessageMatcherFunction = (message: string) => boolean;
|
|
4
|
+
type Karma = {
|
|
5
|
+
log: (level: BrowserConsoleLogOptions["level"], ...data: any[]) => void;
|
|
6
|
+
config: {
|
|
7
|
+
ui5?: {
|
|
8
|
+
config: {
|
|
9
|
+
strictconsoleerrors?: boolean; // Karma options are all lowercase at runtime!
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function wrapPatterns(pattern: RegExp | string): MessageMatcherFunction {
|
|
16
|
+
if (pattern instanceof RegExp) {
|
|
17
|
+
return (message) => message.match(pattern) !== null;
|
|
18
|
+
} else {
|
|
19
|
+
return (message) => message.includes(pattern);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* List of error message patterns that are always accepted.
|
|
25
|
+
*/
|
|
26
|
+
const GLOBALLY_ACCEPTED_ERRORS = [
|
|
27
|
+
"failed to load JavaScript resource: sap/esh/search/ui/i18n.js" // shell
|
|
28
|
+
].map(wrapPatterns);
|
|
29
|
+
|
|
30
|
+
class ConsoleErrorChecker {
|
|
31
|
+
private matchers: MessageMatcherFunction[] = [];
|
|
32
|
+
private messages: string[] = [];
|
|
33
|
+
private readonly karma: Karma | undefined;
|
|
34
|
+
private readonly isStrict: boolean;
|
|
35
|
+
|
|
36
|
+
private readonly observer = new MutationObserver((mutations) => {
|
|
37
|
+
const opaFrame = mutations.reduce((iFrame: HTMLIFrameElement | null, mutation: MutationRecord) => {
|
|
38
|
+
if (iFrame !== null) {
|
|
39
|
+
return iFrame;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const node of Array.from(mutation.addedNodes)) {
|
|
43
|
+
if (node instanceof Element) {
|
|
44
|
+
const element = node.querySelector("#OpaFrame");
|
|
45
|
+
if (element instanceof HTMLIFrameElement && element.contentWindow) {
|
|
46
|
+
return element;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return iFrame;
|
|
52
|
+
}, null);
|
|
53
|
+
|
|
54
|
+
if (opaFrame && opaFrame.contentWindow) {
|
|
55
|
+
this.prepareWindow(opaFrame.contentWindow);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
constructor(window: Window & { __karma__?: Karma }) {
|
|
60
|
+
QUnit.moduleStart(() => {
|
|
61
|
+
this.observer.observe(window.document.body, { childList: true });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
QUnit.moduleDone(() => {
|
|
65
|
+
this.observer.disconnect();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
QUnit.testStart(() => {
|
|
69
|
+
this.reset();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
QUnit.log(() => {
|
|
73
|
+
this.handleFailedMessages();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
this.karma = window.__karma__;
|
|
77
|
+
|
|
78
|
+
// either go for Karma config option "ui5.config.strictConsoleErrors" or use URL query parameter "strict"
|
|
79
|
+
const search = new URLSearchParams(window.location.search);
|
|
80
|
+
const urlParam = search.get("strictConsoleErrors");
|
|
81
|
+
if (urlParam !== null) {
|
|
82
|
+
this.isStrict = urlParam === "true";
|
|
83
|
+
} else {
|
|
84
|
+
this.isStrict = this.karma?.config.ui5?.config.strictconsoleerrors ?? false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.reset();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private handleFailedMessages() {
|
|
91
|
+
const failedMessages = this.messages;
|
|
92
|
+
this.messages = [];
|
|
93
|
+
|
|
94
|
+
if (failedMessages.length > 0) {
|
|
95
|
+
QUnit.assert.pushResult({
|
|
96
|
+
result: false,
|
|
97
|
+
source: "FE Console Log Check",
|
|
98
|
+
message: `There were ${failedMessages.length} unexpected console errors`,
|
|
99
|
+
actual: failedMessages,
|
|
100
|
+
expected: []
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private reset() {
|
|
106
|
+
this.messages = [];
|
|
107
|
+
|
|
108
|
+
// this sets the default to apply if no allowed patterns are set via setAcceptedErrorPatterns().
|
|
109
|
+
if (this.isStrict) {
|
|
110
|
+
this.matchers = GLOBALLY_ACCEPTED_ERRORS;
|
|
111
|
+
} else {
|
|
112
|
+
this.matchers = [() => true];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
setAcceptedErrorPatterns(patterns?: (RegExp | string)[]) {
|
|
117
|
+
if (!patterns || patterns.length === 0) {
|
|
118
|
+
this.matchers = GLOBALLY_ACCEPTED_ERRORS;
|
|
119
|
+
} else {
|
|
120
|
+
this.matchers = patterns.map(wrapPatterns).concat(GLOBALLY_ACCEPTED_ERRORS);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private checkAndLog(type: BrowserConsoleLogOptions["level"], ...data: any[]) {
|
|
125
|
+
// only check the error messages
|
|
126
|
+
if (type === "error") {
|
|
127
|
+
const messageText = data[0];
|
|
128
|
+
const isAllowed = this.matchers.some((matcher) => matcher(messageText));
|
|
129
|
+
if (!isAllowed) {
|
|
130
|
+
this.messages.push(messageText);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (this.karma) {
|
|
135
|
+
// wrap the data to facilitate parsing in the backend
|
|
136
|
+
const wrappedData = data.map((d) => [d]);
|
|
137
|
+
this.karma.log(type, wrappedData);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private prepareWindow(window: Window) {
|
|
142
|
+
const console: Console = (window as any).console;
|
|
143
|
+
|
|
144
|
+
// capture console.log(), console.debug(), etc.
|
|
145
|
+
const patchConsoleMethod = (method: "log" | "info" | "warn" | "error" | "debug") => {
|
|
146
|
+
const fnOriginal = console[method];
|
|
147
|
+
console[method] = (...data: any[]): void => {
|
|
148
|
+
this.checkAndLog(method, ...data);
|
|
149
|
+
return fnOriginal.apply(console, data);
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
patchConsoleMethod("log");
|
|
154
|
+
patchConsoleMethod("debug");
|
|
155
|
+
patchConsoleMethod("info");
|
|
156
|
+
patchConsoleMethod("warn");
|
|
157
|
+
patchConsoleMethod("error");
|
|
158
|
+
|
|
159
|
+
// capture console.assert()
|
|
160
|
+
// see https://console.spec.whatwg.org/#assert
|
|
161
|
+
console.assert = function (condition = false, ...data: any[]) {
|
|
162
|
+
if (condition) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const message = "Assertion failed";
|
|
167
|
+
if (data.length === 0) {
|
|
168
|
+
data.push(message);
|
|
169
|
+
} else {
|
|
170
|
+
let first = data[0];
|
|
171
|
+
if (typeof first !== "string") {
|
|
172
|
+
data.unshift(message);
|
|
173
|
+
} else {
|
|
174
|
+
first = `${message}: ${first}`;
|
|
175
|
+
data[0] = first;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.error(...data);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// capture errors
|
|
183
|
+
function onPromiseRejection(this: ConsoleErrorChecker, event: PromiseRejectionEvent) {
|
|
184
|
+
const message = `UNHANDLED PROMISE REJECTION: ${event.reason}`;
|
|
185
|
+
this.checkAndLog("error", message, event.reason?.stack);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function onError(this: ConsoleErrorChecker, event: ErrorEvent) {
|
|
189
|
+
const message = event.message;
|
|
190
|
+
this.checkAndLog("error", message, event.filename);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
window.addEventListener("error", onError.bind(this), { passive: true });
|
|
194
|
+
window.addEventListener("unhandledrejection", onPromiseRejection.bind(this), { passive: true });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
static getInstance(window: Window & { sapFEConsoleErrorChecker?: ConsoleErrorChecker }): ConsoleErrorChecker {
|
|
198
|
+
// the global instance is needed to support multiple tests in a row (in Karma)
|
|
199
|
+
if (!window.sapFEConsoleErrorChecker) {
|
|
200
|
+
window.sapFEConsoleErrorChecker = new ConsoleErrorChecker(window);
|
|
201
|
+
}
|
|
202
|
+
return window.sapFEConsoleErrorChecker;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export default ConsoleErrorChecker.getInstance(window);
|
|
@@ -10,13 +10,14 @@ sap.ui.define(
|
|
|
10
10
|
"sap/base/util/UriParameters",
|
|
11
11
|
"sap/fe/test/Utils",
|
|
12
12
|
"sap/fe/test/Stubs",
|
|
13
|
-
"sap/fe/test/BaseArrangements"
|
|
13
|
+
"sap/fe/test/BaseArrangements",
|
|
14
|
+
"sap/fe/test/internal/ConsoleErrorChecker"
|
|
14
15
|
],
|
|
15
|
-
function(Opa5, OpaBuilder, UriParameters, Utils, Stubs, BaseArrangements) {
|
|
16
|
+
function (Opa5, OpaBuilder, UriParameters, Utils, Stubs, BaseArrangements, ConsoleErrorChecker) {
|
|
16
17
|
"use strict";
|
|
17
18
|
|
|
18
19
|
return BaseArrangements.extend("sap.fe.test.internal.FEArrangements", {
|
|
19
|
-
constructor: function(mSettings) {
|
|
20
|
+
constructor: function (mSettings) {
|
|
20
21
|
BaseArrangements.call(
|
|
21
22
|
this,
|
|
22
23
|
Utils.mergeObjects(
|
|
@@ -28,7 +29,7 @@ sap.ui.define(
|
|
|
28
29
|
);
|
|
29
30
|
},
|
|
30
31
|
|
|
31
|
-
iResetTestData: function(bIgnoreRedeploy) {
|
|
32
|
+
iResetTestData: function (bIgnoreRedeploy) {
|
|
32
33
|
var that = this,
|
|
33
34
|
oUriParams = new UriParameters(window.location.href),
|
|
34
35
|
sBackendUrl = oUriParams.get("useBackendUrl"),
|
|
@@ -40,26 +41,45 @@ sap.ui.define(
|
|
|
40
41
|
: "default";
|
|
41
42
|
|
|
42
43
|
return OpaBuilder.create(this)
|
|
43
|
-
.success(function() {
|
|
44
|
+
.success(function () {
|
|
44
45
|
var oResetData = that.resetTestData(),
|
|
45
46
|
oRedeploy = bIgnoreRedeploy ? Promise.resolve() : jQuery.post(sProxyPrefix + "/redeploy?tenant=" + sTenantID);
|
|
46
47
|
|
|
47
48
|
Promise.all([oResetData, oRedeploy])
|
|
48
|
-
.finally(function() {
|
|
49
|
+
.finally(function () {
|
|
49
50
|
bSuccess = true;
|
|
50
51
|
})
|
|
51
|
-
.catch(function(oError) {
|
|
52
|
+
.catch(function (oError) {
|
|
52
53
|
throw oError;
|
|
53
54
|
});
|
|
54
55
|
|
|
55
56
|
return OpaBuilder.create(this)
|
|
56
|
-
.check(function() {
|
|
57
|
+
.check(function () {
|
|
57
58
|
return bSuccess;
|
|
58
59
|
})
|
|
59
60
|
.execute();
|
|
60
61
|
})
|
|
61
62
|
.description(Utils.formatMessage("Reset test data on tenant '{0}'", sTenantID))
|
|
62
63
|
.execute();
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Fail the test if there are errors logged to the browser console.
|
|
68
|
+
*
|
|
69
|
+
* @param {Array<RegExp|string>} [aErrors] The allowed error messages. Either use regular expressions or strings. Pass undefined or an empty array to reject all error messages.
|
|
70
|
+
* @returns {*}
|
|
71
|
+
*/
|
|
72
|
+
iAcceptTheseErrors: function (aErrors) {
|
|
73
|
+
return OpaBuilder.create(this)
|
|
74
|
+
.do(function () {
|
|
75
|
+
ConsoleErrorChecker.setAcceptedErrorPatterns(aErrors);
|
|
76
|
+
})
|
|
77
|
+
.description(
|
|
78
|
+
!aErrors || aErrors.length === 0
|
|
79
|
+
? "Do not accept error messages"
|
|
80
|
+
: Utils.formatMessage("Only accept these error message patterns: {0}", aErrors)
|
|
81
|
+
)
|
|
82
|
+
.execute();
|
|
63
83
|
}
|
|
64
84
|
});
|
|
65
85
|
}
|
|
@@ -21,7 +21,7 @@ sap.ui.define(["sap/ui/core/Core", "sap/ui/core/library"], function (Core, _libr
|
|
|
21
21
|
controls: [],
|
|
22
22
|
elements: [],
|
|
23
23
|
// eslint-disable-next-line no-template-curly-in-string
|
|
24
|
-
version: "1.
|
|
24
|
+
version: "1.104.1",
|
|
25
25
|
noLibraryCSS: true
|
|
26
26
|
});
|
|
27
27
|
return thisLib;
|