wdio-ui5-service
Version:
WebdriverIO plugin for testing UI5 browser-based apps
937 lines • 50.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.setup = setup;
exports.start = start;
exports.injectUI5 = injectUI5;
exports.checkForUI5Page = checkForUI5Page;
exports.authenticate = authenticate;
exports._addWdi5Commands = _addWdi5Commands;
var path_1 = require("path");
var promises_1 = require("fs/promises");
var os_1 = require("os");
var semver = __importStar(require("semver"));
var marky_1 = require("marky");
var wdi5_control_js_1 = require("./wdi5-control.js");
var wdi5_fe_js_1 = require("./wdi5-fe.js");
var injectTools_cjs_1 = require("../../client-side-js/injectTools.cjs");
var injectUI5_cjs_1 = require("../../client-side-js/injectUI5.cjs");
var injectXHRPatch_cjs_1 = require("../../client-side-js/injectXHRPatch.cjs");
var getSelectorForElement_cjs_1 = require("../../client-side-js/getSelectorForElement.cjs");
var _checkForUI5Ready_cjs_1 = require("../../client-side-js/_checkForUI5Ready.cjs");
var getObject_cjs_1 = require("../../client-side-js/getObject.cjs");
var getUI5Version_cjs_1 = require("../../client-side-js/getUI5Version.cjs");
var _navTo_cjs_1 = require("../../client-side-js/_navTo.cjs");
var allControls_cjs_1 = require("../../client-side-js/allControls.cjs");
var Logger_js_1 = require("./Logger.js");
var wdi5_object_js_1 = require("./wdi5-object.js");
var BTPAuthenticator_js_1 = __importDefault(require("./authentication/BTPAuthenticator.js"));
var BasicAuthenticator_js_1 = __importDefault(require("./authentication/BasicAuthenticator.js"));
var CustomAuthenticator_js_1 = __importDefault(require("./authentication/CustomAuthenticator.js"));
var Office365Authenticator_js_1 = __importDefault(require("./authentication/Office365Authenticator.js"));
var CertAuthenticator_js_1 = __importDefault(require("./authentication/CertAuthenticator.js"));
var Logger = Logger_js_1.Logger.getInstance();
/** store the status of initialization */
var _isInitialized = false;
/** stores the status of the setup process */
var _setupComplete = false;
/** currently running sap.ui.version */
var _sapUI5Version = null;
/** relay runtime config options from Service */
var _config = null;
function setup(config) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
_config = config;
if (_setupComplete) {
// already setup done
return [2 /*return*/];
}
// jump-start the desired log level
Logger.setLogLevel(config.wdi5.logLevel || "error");
if (browser.isMultiremote) {
;
browser.instances.forEach(function (name) {
initBrowser(browser[name]);
});
initMultiRemoteBrowser();
}
else {
initBrowser(browser);
}
_setupComplete = true;
return [2 /*return*/];
});
});
}
function start(config) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!config.wdi5.url) return [3 /*break*/, 2];
// still support the old logic that we don't have breaking changes
Logger.warn("'url' property in config file deprecated: please use 'baseUrl' only!");
Logger.info("open url: ".concat(config.wdi5.url));
return [4 /*yield*/, browser.url(config.wdi5.url)];
case 1:
_a.sent();
return [3 /*break*/, 4];
case 2:
Logger.info("open url: ".concat(config.baseUrl));
return [4 /*yield*/, browser.url(config.baseUrl)];
case 3:
_a.sent();
_a.label = 4;
case 4: return [2 /*return*/];
}
});
});
}
function initMultiRemoteBrowser() {
var _this = this;
;
["asControl", "goTo", "screenshot", "waitForUI5", "getUI5Version", "getSelectorForElement", "allControls"].forEach(function (command) {
browser.addCommand(command, function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return __awaiter(_this, void 0, void 0, function () {
var multiRemoteInstance, result;
var _this = this;
return __generator(this, function (_a) {
multiRemoteInstance = browser;
result = [];
multiRemoteInstance.instances.forEach(function (name) {
result.push(multiRemoteInstance[name][command].apply(_this, args));
});
return [2 /*return*/, Promise.all(result)];
});
});
});
});
}
function initBrowser(browserInstance) {
// init control cache
if (!browserInstance._controls) {
Logger.info("creating internal control map");
browserInstance._controls = [];
}
_addWdi5Commands(browserInstance);
if (!browserInstance.fe) {
;
browserInstance.fe = wdi5_fe_js_1.WDI5FE;
}
_setupComplete = true;
}
function checkUI5Version(ui5Version) {
if (semver.lt(ui5Version, "1.60.0")) {
// the record replay api is only available since 1.60
Logger.error("The UI5 version of your application is too low. Minimum required is 1.60!");
throw new Error("The UI5 version of your application is too low. Minimum required is 1.60!");
}
}
/**
* function library to setup the webdriver to UI5 bridge, it runs all the initial setup
* make sap/ui/test/RecordReplay accessible via wdio
* attach the sap/ui/test/RecordReplay object to the application context window object as 'bridge'
*/
function injectUI5(config, browserInstance) {
return __awaiter(this, void 0, void 0, function () {
var waitForUI5Timeout, result, timeout, _a, _b, _c, _d, _e, version, _f;
return __generator(this, function (_g) {
switch (_g.label) {
case 0:
if (!(config === null || config === void 0 ? void 0 : config.wdi5)) {
//Fetching config from global variable
config.wdi5 = global.__wdi5Config.wdi5;
}
waitForUI5Timeout = config.wdi5.waitForUI5Timeout || 15000;
result = true;
timeout = waitForUI5Timeout + 1000;
return [4 /*yield*/, browserInstance.setTimeout({ script: timeout })];
case 1:
_g.sent();
Logger.debug("browser script timeout set to ".concat(timeout));
if (!(typeof browserInstance.getTimeouts === "function")) return [3 /*break*/, 3];
_b = (_a = Logger).debug;
_c = "browser timeouts are ".concat;
_e = (_d = JSON).stringify;
return [4 /*yield*/, browserInstance.getTimeouts()];
case 2:
_b.apply(_a, [_c.apply("browser timeouts are ", [_e.apply(_d, [_g.sent(), null, 2])])]);
_g.label = 3;
case 3: return [4 /*yield*/, browserInstance.getUI5Version()];
case 4:
version = _g.sent();
checkUI5Version(version);
return [4 /*yield*/, (0, injectTools_cjs_1.clientSide_injectTools)(browserInstance)]; // helpers for wdi5 browser scope
case 5:
_g.sent(); // helpers for wdi5 browser scope
return [4 /*yield*/, (0, injectXHRPatch_cjs_1.clientSide_injectXHRPatch)(config, browserInstance)];
case 6:
_g.sent();
_f = result;
if (!_f) return [3 /*break*/, 8];
return [4 /*yield*/, (0, injectUI5_cjs_1.clientSide_injectUI5)(config, waitForUI5Timeout, browserInstance)];
case 7:
_f = (_g.sent());
_g.label = 8;
case 8:
result = _f;
// we are not using _controls as an array, we are using it as an object. That's why the length property
// is not updated right away: https://stackoverflow.com/a/4424026
if (Object.keys(browserInstance._controls).length > 0) {
Logger.info("invalidating control map!");
browserInstance._controls = [];
}
if (result) {
// set when call returns
_isInitialized = true;
Logger.success("successfully initialized wdio-ui5 bridge");
}
else {
Logger.error("bridge was not initialized correctly");
}
return [2 /*return*/, result];
}
});
});
}
function checkForUI5Page(browserInstance) {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// wait till the loading finished and the state is "completed"
return [4 /*yield*/, browserInstance.waitUntil(function () { return __awaiter(_this, void 0, void 0, function () {
var checkState;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, browserInstance.executeAsync(function (done) {
done({ state: document.readyState, sapReady: !!window.sap });
})];
case 1:
checkState = _a.sent();
return [2 /*return*/, checkState.state === "complete" && checkState.sapReady];
}
});
}); })
// sap in global window namespace denotes (most likely :) ) that ui5 is present
];
case 1:
// wait till the loading finished and the state is "completed"
_a.sent();
return [4 /*yield*/, browserInstance.executeAsync(function (done) {
done(!!window.sap);
})];
case 2:
// sap in global window namespace denotes (most likely :) ) that ui5 is present
return [2 /*return*/, _a.sent()];
}
});
});
}
function authenticate(options, browserInstanceName) {
return __awaiter(this, void 0, void 0, function () {
var _a, btp;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_a = options.provider;
switch (_a) {
case "BTP": return [3 /*break*/, 1];
case "Certificate": return [3 /*break*/, 5];
case "BasicAuth": return [3 /*break*/, 7];
case "Office365": return [3 /*break*/, 9];
case "custom": return [3 /*break*/, 11];
}
return [3 /*break*/, 13];
case 1:
btp = new BTPAuthenticator_js_1.default(options, browserInstanceName);
if (!options.disableBiometricAuthentication) return [3 /*break*/, 3];
return [4 /*yield*/, btp.disableBiometricAuthentication()];
case 2:
_b.sent();
_b.label = 3;
case 3: return [4 /*yield*/, btp.login()];
case 4:
_b.sent();
return [3 /*break*/, 14];
case 5: return [4 /*yield*/, new CertAuthenticator_js_1.default(options, browserInstanceName, _config.baseUrl).login()];
case 6:
_b.sent();
return [3 /*break*/, 14];
case 7: return [4 /*yield*/, new BasicAuthenticator_js_1.default(options, browserInstanceName, _config.baseUrl).login()];
case 8:
_b.sent();
return [3 /*break*/, 14];
case 9: return [4 /*yield*/, new Office365Authenticator_js_1.default(options, browserInstanceName).login()];
case 10:
_b.sent();
return [3 /*break*/, 14];
case 11: return [4 /*yield*/, new CustomAuthenticator_js_1.default(options, browserInstanceName).login()];
case 12:
_b.sent();
_b.label = 13;
case 13: return [3 /*break*/, 14];
case 14: return [2 /*return*/];
}
});
});
}
//******************************************************************************************
// private
/**
* creates a string valid as object key from a selector
* @param selector
* @returns wdio_ui5_key
*/
function _createWdioUI5KeyFromSelector(selector) {
var orEmpty = function (string) { return string || ""; };
var _selector = selector.selector;
var wdi5_ui5_key = "".concat(orEmpty(_selector.id)).concat(orEmpty(_selector.viewName)).concat(orEmpty(_selector.controlType)).concat(orEmpty(JSON.stringify(_selector.bindingPath))).concat(orEmpty(JSON.stringify(_selector.i18NText))).concat(orEmpty(JSON.stringify(_selector.descendant))).concat(orEmpty(JSON.stringify(_selector.labelFor))).concat(orEmpty(JSON.stringify(_selector.properties))).concat(orEmpty(JSON.stringify(_selector.ancestor))).replace(/[^0-9a-zA-Z]+/, "");
return wdi5_ui5_key;
}
/**
* does a basic validation of a wdi5ControlSelector
* @param wdi5Selector: wdi5Selector
* @returns {boolean} if the given selector is a valid selector
*/
function _verifySelector(wdi5Selector) {
if (wdi5Selector.hasOwnProperty("selector")) {
if (wdi5Selector.selector.hasOwnProperty("id") ||
wdi5Selector.selector.hasOwnProperty("viewName") ||
wdi5Selector.selector.hasOwnProperty("bindingPath") ||
wdi5Selector.selector.hasOwnProperty("controlType") ||
wdi5Selector.selector.hasOwnProperty("i18NText") ||
wdi5Selector.selector.hasOwnProperty("labelFor") ||
wdi5Selector.selector.hasOwnProperty("descendant") ||
wdi5Selector.selector.hasOwnProperty("ancestor") ||
wdi5Selector.selector.hasOwnProperty("properties") ||
wdi5Selector.selector.hasOwnProperty("sibling") ||
wdi5Selector.selector.hasOwnProperty("interactable")) {
return true;
}
Logger.error("Specified selector is not valid. Please use at least one of: 'id, viewName, bindingPath, controlType, i18NText, labelFor, ancestor, properties, descendant, sibling, interactable' -> abort");
return false;
}
Logger.error("Specified selector is not valid -> property 'selector' is missing");
return false;
}
function _addWdi5Commands(browserInstance) {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
browserInstance.addCommand("_asControl", function (wdi5Selector) { return __awaiter(_this, void 0, void 0, function () {
var internalKey, wdi5Control, e;
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (!_verifySelector(wdi5Selector)) {
return [2 /*return*/, "ERROR: Specified selector is not valid -> abort"];
}
internalKey = wdi5Selector.wdio_ui5_key || _createWdioUI5KeyFromSelector(wdi5Selector);
if (!(!((_a = browserInstance._controls) === null || _a === void 0 ? void 0 : _a[internalKey]) || wdi5Selector.forceSelect) /* always retrieve control */) return [3 /*break*/, 2]; /* always retrieve control */
Logger.info("creating internal control with id ".concat(internalKey));
wdi5Selector.wdio_ui5_key = internalKey;
(0, marky_1.mark)("retrieveSingleControl"); //> TODO: bind to debug log level
return [4 /*yield*/, new wdi5_control_js_1.WDI5Control({ browserInstance: browserInstance }).init(wdi5Selector, wdi5Selector.forceSelect)];
case 1:
wdi5Control = _b.sent();
e = (0, marky_1.stop)("retrieveSingleControl") //> TODO: bind to debug log level
;
Logger.info("_asControl() needed ".concat(e.duration, " for ").concat(internalKey));
browserInstance._controls[internalKey] = wdi5Control;
return [3 /*break*/, 3];
case 2:
Logger.info("reusing internal control with id ".concat(internalKey));
_b.label = 3;
case 3: return [2 /*return*/, browserInstance._controls[internalKey]];
}
});
}); });
browser.addCommand("asObject", function (_uuid) { return __awaiter(_this, void 0, void 0, function () {
var _result, uuid, status, aProtoFunctions, className, object, wdiObject;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, (0, getObject_cjs_1.clientSide_getObject)(_uuid)];
case 1:
_result = (_a.sent());
uuid = _result.uuid, status = _result.status, aProtoFunctions = _result.aProtoFunctions, className = _result.className, object = _result.object;
if (status === 0) {
wdiObject = new wdi5_object_js_1.WDI5Object(uuid, aProtoFunctions, object);
return [2 /*return*/, wdiObject];
}
_writeObjectResultLog(_result, "asObject()");
return [2 /*return*/, { status: status, aProtoFunctions: aProtoFunctions, className: className, uuid: uuid }];
}
});
}); });
// no fluent API -> no private method
browserInstance.addCommand("allControls", function (wdi5Selector) { return __awaiter(_this, void 0, void 0, function () {
var internalKey, _a, _b;
var _c;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
if (!_verifySelector(wdi5Selector)) {
return [2 /*return*/, "ERROR: Specified selector is not valid -> abort"];
}
internalKey = wdi5Selector.wdio_ui5_key || _createWdioUI5KeyFromSelector(wdi5Selector);
if (!(!((_c = browserInstance._controls) === null || _c === void 0 ? void 0 : _c[internalKey]) || wdi5Selector.forceSelect) /* always retrieve control */) return [3 /*break*/, 2]; /* always retrieve control */
wdi5Selector.wdio_ui5_key = internalKey;
Logger.info("creating internal controls with id ".concat(internalKey));
_a = browserInstance._controls;
_b = internalKey;
return [4 /*yield*/, _allControls(wdi5Selector, browserInstance)];
case 1:
_a[_b] = _d.sent();
return [2 /*return*/, browserInstance._controls[internalKey]];
case 2:
Logger.info("reusing internal control with id ".concat(internalKey));
_d.label = 3;
case 3: return [2 /*return*/, browserInstance._controls[internalKey]];
}
});
}); });
/**
* Find the best control selector for a DOM element. A selector uniquely represents a single element.
* The 'best' selector is the one with which it is most likely to uniquely identify a control with the least possible inspection of the control tree.
* @param {object} oOptions
* @param {object} oOptions.domElement - DOM Element to search for
* @param {object} oOptions.settings - ui5 settings object
* @param {boolean} oOptions.settings.preferViewId
*/
browserInstance.addCommand("getSelectorForElement", function (oOptions) { return __awaiter(_this, void 0, void 0, function () {
var result;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, (0, getSelectorForElement_cjs_1.clientSide_getSelectorForElement)(oOptions, browserInstance)];
case 1:
result = (_a.sent());
if (result.status === 1) {
console.error("ERROR: getSelectorForElement() failed because of: " + result.message);
return [2 /*return*/, result.message];
}
else if (result.status === 0) {
console.log("SUCCESS: getSelectorForElement() returned: ".concat(JSON.stringify(result.result)));
return [2 /*return*/, result.result];
}
return [2 /*return*/];
}
});
}); });
browserInstance.addCommand("getUI5Version", function () { return __awaiter(_this, void 0, void 0, function () {
var resultVersion;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!!_sapUI5Version) return [3 /*break*/, 2];
return [4 /*yield*/, (0, getUI5Version_cjs_1.clientSide_getUI5Version)(browserInstance)];
case 1:
resultVersion = _a.sent();
_sapUI5Version = resultVersion;
_a.label = 2;
case 2: return [2 /*return*/, _sapUI5Version];
}
});
}); });
/**
* uses the UI5 native waitForUI5 function to wait for all promises to be settled
*/
browserInstance.addCommand("waitForUI5", function () { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, _waitForUI5(browserInstance)];
case 1: return [2 /*return*/, _a.sent()];
}
});
}); });
/**
* wait for ui5 and take a screenshot
*/
browserInstance.addCommand("screenshot", function (fileAppendix) { return __awaiter(_this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, _waitForUI5(browserInstance)];
case 1:
_a.sent();
return [4 /*yield*/, _writeScreenshot(fileAppendix)];
case 2:
_a.sent();
return [2 /*return*/];
}
});
}); });
browserInstance.addCommand("goTo", function (oOptions) { return __awaiter(_this, void 0, void 0, function () {
var sHash, oRoute, url, _a, currentUrl, alreadyNavByHash, navToRoot;
var _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
if (typeof oOptions === "string") {
sHash = oOptions;
}
else {
sHash = oOptions.sHash;
}
oRoute = oOptions.oRoute;
if (!(sHash && sHash.length > 0)) return [3 /*break*/, 16];
if (!((_b = browserInstance.options.wdi5) === null || _b === void 0 ? void 0 : _b.url)) return [3 /*break*/, 13];
_a = browserInstance.options.wdi5["url"];
if (_a) return [3 /*break*/, 2];
return [4 /*yield*/, browserInstance.getUrl()];
case 1:
_a = (_c.sent());
_c.label = 2;
case 2:
url = _a;
if (!(url && url.length > 0 && url !== "#")) return [3 /*break*/, 8];
return [4 /*yield*/, browserInstance.getUrl()];
case 3:
currentUrl = _c.sent();
alreadyNavByHash = currentUrl.includes("#");
navToRoot = url.startsWith("/");
if (!(alreadyNavByHash && !navToRoot)) return [3 /*break*/, 5];
return [4 /*yield*/, browserInstance.url("".concat(currentUrl.split("#")[0]).concat(sHash))];
case 4:
_c.sent();
return [3 /*break*/, 7];
case 5: return [4 /*yield*/, browserInstance.url("".concat(url).concat(sHash))];
case 6:
_c.sent();
_c.label = 7;
case 7: return [3 /*break*/, 12];
case 8:
if (!(url && url.length > 0 && url === "#")) return [3 /*break*/, 10];
// route without the double hash
return [4 /*yield*/, browserInstance.url("".concat(sHash))];
case 9:
// route without the double hash
_c.sent();
return [3 /*break*/, 12];
case 10:
// just a fallback
return [4 /*yield*/, browserInstance.url("".concat(sHash))];
case 11:
// just a fallback
_c.sent();
_c.label = 12;
case 12: return [3 /*break*/, 15];
case 13: return [4 /*yield*/, browserInstance.url(sHash)];
case 14:
_c.sent();
_c.label = 15;
case 15: return [3 /*break*/, 19];
case 16:
if (!(oRoute && oRoute.sName)) return [3 /*break*/, 18];
// navigate using the ui5 router
// sComponentId, sName, oParameters, oComponentTargetInfo, bReplace
return [4 /*yield*/, _navTo(oRoute.sComponentId, oRoute.sName, oRoute.oParameters, oRoute.oComponentTargetInfo, oRoute.bReplace, browserInstance)];
case 17:
// navigate using the ui5 router
// sComponentId, sName, oParameters, oComponentTargetInfo, bReplace
_c.sent();
return [3 /*break*/, 19];
case 18:
Logger.error("ERROR: navigating to another page");
_c.label = 19;
case 19: return [2 /*return*/];
}
});
}); });
// inspired by and after staring a long time hard at:
// https://stackoverflow.com/questions/51635378/keep-object-chainable-using-async-methods
// https://github.com/Shigma/prochain
// https://github.com/l8js/l8/blob/main/src/core/liquify.js
// channel the async function browser._asControl (init'ed via browser.addCommand above) through a Proxy
// in order to chain calls of any subsequent UI5 api methods on the retrieved UI5 control:
// await browser.asControl(selector).methodOfUI5control().anotherMethodOfUI5control()
// the way this works is twofold:
// 1. (almost) all UI5 $control's API methods are reinjected from the browser-scope
// into the Node.js scope via async wdi5._executeControlMethod(), which in term actually calls
// the reinjected API method within the browser scope
// 2. the execution of each UI5 $control's API method (via async wdi5._executeControlMethod() => Promise) is then chained
// via the below "then"-ing of the (async wdi5._executeControlMethod() => Promise)-Promises with the help of
// the a Proxy and a recursive `handler` function
if (!browserInstance.asControl) {
browserInstance.asControl = function (ui5ControlSelector) {
var _a;
var asyncMethods = ["then", "catch", "finally"];
var functionQueue = [];
// we need to do the same operation as in the 'init' of 'wdi5-control.ts'
var logging = (_a = ui5ControlSelector === null || ui5ControlSelector === void 0 ? void 0 : ui5ControlSelector.logging) !== null && _a !== void 0 ? _a : true;
function makeFluent(target) {
var promise = Promise.resolve(target);
var handler = {
get: function (_, prop) {
functionQueue.push(prop);
return asyncMethods.includes(prop)
? function () {
var boundArgs = [];
for (var _i = 0; _i < arguments.length; _i++) {
boundArgs[_i] = arguments[_i];
}
return makeFluent(promise[prop].apply(promise, __spreadArray([], __read(boundArgs), false)));
}
: makeFluent(promise.then(function (object) {
// when object is undefined the previous function call failed
try {
return object[prop];
// eslint-disable-next-line @typescript-eslint/no-unused-vars
}
catch (error) {
// different node versions return a different `error.message` so we use our own message
if (logging) {
Logger.error("Cannot read property '".concat(prop, "' in the execution queue!"));
}
}
}));
},
apply: function (_, thisArg, boundArgs) {
return makeFluent(
// When "targetFunction" is empty we can assume that there are errors in the execution queue
promise.then(function (targetFunction) {
if (targetFunction) {
return Reflect.apply(targetFunction, thisArg, boundArgs);
}
else {
// a functionQueue without a 'then' can be ignored
// as the original error was already logged
if (functionQueue.includes("then") && logging) {
functionQueue.splice(functionQueue.indexOf("then"));
Logger.error("One of the calls in the queue \"".concat(functionQueue.join("()."), "()\" previously failed!"));
}
}
}));
}
};
// eslint-disable-next-line @typescript-eslint/no-empty-function
return new Proxy(function () { }, handler);
}
// @ts-expect-error as design-time is not aware of _asControl
return makeFluent(browserInstance._asControl(ui5ControlSelector));
};
}
return [2 /*return*/];
});
});
}
/**
* retrieve a DOM element via UI5 locator
* @param {sap.ui.test.RecordReplay.ControlSelector} controlSelector
* @return {[WebdriverIO.Element | String, [aProtoFunctions]]} UI5 control or error message, array of function names of this control
*/
function _allControls() {
return __awaiter(this, arguments, void 0, function (controlSelector, browserInstance) {
var response, retrievedElements, resultWDi5Elements, _a, retrievedElements_1, retrievedElements_1_1, cControl, oOptions, e_1_1;
var _b, e_1, _c, _d;
var _e, _f;
if (controlSelector === void 0) { controlSelector = this._controlSelector; }
if (browserInstance === void 0) { browserInstance = browser; }
return __generator(this, function (_g) {
switch (_g.label) {
case 0:
// check whether we have a "by id regex" locator request
if (controlSelector.selector.id && typeof controlSelector.selector.id === "object") {
// make it a string for serializing into browser-scope and
// further processing there
controlSelector.selector.id = controlSelector.selector.id.toString();
}
if (typeof ((_e = controlSelector.selector.properties) === null || _e === void 0 ? void 0 : _e.text) === "object" &&
((_f = controlSelector.selector.properties) === null || _f === void 0 ? void 0 : _f.text) instanceof RegExp) {
// make it a string for serializing into browser-scope and
// further processing there
controlSelector.selector.properties.text = controlSelector.selector.properties.text.toString();
}
return [4 /*yield*/, (0, allControls_cjs_1.clientSide_allControls)(controlSelector, browserInstance)];
case 1:
response = _g.sent();
_writeObjectResultLog(response, "allControls()");
if (!(response.status === 0)) return [3 /*break*/, 14];
retrievedElements = response.result;
resultWDi5Elements = [];
_g.label = 2;
case 2:
_g.trys.push([2, 7, 8, 13]);
_a = true, retrievedElements_1 = __asyncValues(retrievedElements);
_g.label = 3;
case 3: return [4 /*yield*/, retrievedElements_1.next()];
case 4:
if (!(retrievedElements_1_1 = _g.sent(), _b = retrievedElements_1_1.done, !_b)) return [3 /*break*/, 6];
_d = retrievedElements_1_1.value;
_a = false;
cControl = _d;
oOptions = {
controlSelector: controlSelector,
wdio_ui5_key: controlSelector.wdio_ui5_key,
forceSelect: controlSelector.forceSelect,
generatedUI5Methods: cControl.aProtoFunctions,
webdriverRepresentation: null,
webElement: cControl.domElement,
domId: cControl.id,
browserInstance: browserInstance
};
// FIXME: multi remote support by providing browserInstance in constructor
resultWDi5Elements.push(new wdi5_control_js_1.WDI5Control(oOptions));
_g.label = 5;
case 5:
_a = true;
return [3 /*break*/, 3];
case 6: return [3 /*break*/, 13];
case 7:
e_1_1 = _g.sent();
e_1 = { error: e_1_1 };
return [3 /*break*/, 13];
case 8:
_g.trys.push([8, , 11, 12]);
if (!(!_a && !_b && (_c = retrievedElements_1.return))) return [3 /*break*/, 10];
return [4 /*yield*/, _c.call(retrievedElements_1)];
case 9:
_g.sent();
_g.label = 10;
case 10: return [3 /*break*/, 12];
case 11:
if (e_1) throw e_1.error;
return [7 /*endfinally*/];
case 12: return [7 /*endfinally*/];
case 13: return [2 /*return*/, resultWDi5Elements];
case 14: return [2 /*return*/, "[wdi5] Error: fetch multiple elements failed: " + response.message];
}
});
});
}
/**
* can be called to make sure before you access any eg. DOM Node the ui5 framework is done loading
* @returns {Boolean} if the UI5 page is fully loaded and ready to interact.
*/
function _waitForUI5(browserInstance) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!_isInitialized) return [3 /*break*/, 2];
return [4 /*yield*/, _checkForUI5Ready(browserInstance)];
case 1:
// injectUI5 was already called and was successful attached
return [2 /*return*/, _a.sent()];
case 2: return [4 /*yield*/, injectUI5(_config, browserInstance)];
case 3:
if (!_a.sent()) return [3 /*break*/, 5];
return [4 /*yield*/, _checkForUI5Ready(browserInstance)];
case 4: return [2 /*return*/, _a.sent()];
case 5: return [2 /*return*/, false];
}
});
});
}
/**
* check for UI5 via the RecordReplay.waitForUI5 method
*/
function _checkForUI5Ready(browserInstance) {
return __awaiter(this, void 0, void 0, function () {
var ready;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
ready = false;
if (!_isInitialized) return [3 /*break*/, 2];
return [4 /*yield*/, (0, _checkForUI5Ready_cjs_1.clientSide__checkForUI5Ready)(browserInstance)];
case 1:
// can only be executed when RecordReplay is attached
return [2 /*return*/, _a.sent()];
case 2: return [2 /*return*/, ready];
}
});
});
}
/**
* @param fileAppendix
*/
function _writeScreenshot() {
return __awaiter(this, arguments, void 0, function (fileAppendix) {
var screenshot, seed, _path, path, error_1;
if (fileAppendix === void 0) { fileAppendix = "-screenshot"; }
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// if config param screenshotsDisabled is set to true -> no screenshots will be taken
if (_config.wdi5["screenshotsDisabled"]) {
Logger.warn("screenshot skipped due to config parameter");
return [2 /*return*/];
}
return [4 /*yield*/, browser.takeScreenshot()];
case 1:
screenshot = _a.sent();
seed = _getDateString();
_path = _config.wdi5.screenshotPath || (0, os_1.tmpdir)();
path = (0, path_1.resolve)(_path, "".concat(seed, "-").concat(fileAppendix, ".png"));
_a.label = 2;
case 2:
_a.trys.push([2, 4, , 5]);
return [4 /*yield*/, (0, promises_1.writeFile)(path, screenshot, "base64")];
case 3:
_a.sent();
Logger.success("screenshot at ".concat(path, " created"));
return [3 /*break*/, 5];
case 4:
error_1 = _a.sent();
Logger.error("error while saving screenshot: ".concat(error_1));
return [3 /*break*/, 5];
case 5: return [2 /*return*/];
}
});
});
}
/**
* generates date string with format M-d-hh-mm-ss
* @returns {String}
*/
function _getDateString() {
var x = new Date();
return "".concat(x.getMonth() + 1, "-").concat(x.getDate(), "-").concat(x.getHours(), "-").concat(x.getMinutes(), "-").concat(x.getSeconds());
}
/**
* navigates to a UI5 route using the Component router
* @param {String} sComponentId
* @param {String} sName
* @param {Object} oParameters
* @param {Object} oComponentTargetInfo
* @param {Boolean} bReplace
*/
function _navTo(sComponentId, sName, oParameters, oComponentTargetInfo, bReplace, browserInstance) {
return __awaiter(this, void 0, void 0, function () {
var result;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, (0, _navTo_cjs_1.clientSide__navTo)(sComponentId, sName, oParameters, oComponentTargetInfo, bReplace, browserInstance)];
case 1:
result = (_a.sent());
if (result.status === 1) {
Logger.error("ERROR: navigation using UI5 router failed because of: " + result.message);
return [2 /*return*/, result.result];
}
else if (result.status === 0) {
Logger.info("SUCCESS: navigation using UI5 router to hash: ".concat(JSON.stringify(result.status)));
return [2 /*return*/, result.result];
}
return [2 /*return*/];
}
});
});
}
/**
* create log based on the status of result.status
* @param {Array} result
* @param {*} functionName
*/
function _writeObjectResultLog(response, functionName) {
if (response.status > 0) {
Logger.error("call of ".concat(functionName,