From 8c53fe0442fee52a0305e5fafc2f00c60636e76d Mon Sep 17 00:00:00 2001 From: "Kireilis, Edvardas" Date: Thu, 7 Apr 2022 21:15:59 +1200 Subject: [PATCH] fix: using custom waitForUI5 function to overcome sap.ui.test.RecordReplay promiseWaiter issue issue: js-soft wdi5 github issues 196 --- .gitignore | 1 + client-side-js/executeControlMethod.js | 113 +++++++++++++----------- client-side-js/fireEvent.js | 50 ++++++----- client-side-js/getControl.js | 43 +++++---- client-side-js/getSelectorForElement.js | 35 ++++---- client-side-js/injectUI5.js | 16 ++++ client-side-js/interactWithControl.js | 31 ++++--- 7 files changed, 168 insertions(+), 121 deletions(-) diff --git a/.gitignore b/.gitignore index d0411d84..7c94303b 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,7 @@ dist/**/* # multi-version test artifacts examples/ui5-js-app/wdio-wdi5* examples/ui5-js-app/webapp/index-*.html +examples/ui5-js-app/dist/**/* # .env diff --git a/client-side-js/executeControlMethod.js b/client-side-js/executeControlMethod.js index 17f6816a..1adc1935 100644 --- a/client-side-js/executeControlMethod.js +++ b/client-side-js/executeControlMethod.js @@ -1,68 +1,75 @@ async function clientSide_executeControlMethod(webElement, methodName, args) { return await browser.executeAsync( (webElement, methodName, args, done) => { - window.bridge.waitForUI5(window.wdi5.waitForUI5Options).then(() => { - // DOM to UI5 - const oControl = window.wdi5.getUI5CtlForWebObj(webElement) - // execute the function - let result = oControl[methodName].apply(oControl, args) - const metadata = oControl.getMetadata() - if (Array.isArray(result)) { - if (result.length === 0) { - done(["success", result, "empty"]) - } else if (result[0]?.getParent) { - // expect the method call delivers non-primitive results (like getId()) - // but delivers a complex/structured type - // -> currenlty, only getAggregation(...) is supported + window.wdi5.waitForUI5( + window.wdi5.waitForUI5Options, + () => { + // DOM to UI5 + const oControl = window.wdi5.getUI5CtlForWebObj(webElement) + // execute the function + let result = oControl[methodName].apply(oControl, args) + const metadata = oControl.getMetadata() + if (Array.isArray(result)) { + if (result.length === 0) { + done(["success", result, "empty"]) + } else if (result[0]?.getParent) { + // expect the method call delivers non-primitive results (like getId()) + // but delivers a complex/structured type + // -> currenlty, only getAggregation(...) is supported - // read classname eg. sap.m.ComboBox - controlType = oControl.getMetadata()._sClassName + // read classname eg. sap.m.ComboBox + controlType = oControl.getMetadata()._sClassName - result = window.wdi5.createControlIdMap(result, controlType) - done(["success", result, "aggregation"]) - } else { - done(["success", result, "result"]) - } - } else { - // ui5 api .focus() doesn't have return value - if (methodName === "focus" && result === undefined) { - done([ - "success", - `called focus() on wdi5 representation of a ${metadata.getElementName()}`, - "element" - ]) - } else if (result === undefined || result === null) { - done([ - "error", - `function ${methodName} does not exist on control ${metadata.getElementName()}!`, - "none" - ]) - } else { - // result mus be a primitive - if (window.wdi5.isPrimitive(result)) { - // getter + result = window.wdi5.createControlIdMap(result, controlType) + done(["success", result, "aggregation"]) + } else { done(["success", result, "result"]) + } + } else { + // ui5 api .focus() doesn't have return value + if (methodName === "focus" && result === undefined) { + done([ + "success", + `called focus() on wdi5 representation of a ${metadata.getElementName()}`, + "element" + ]) + } else if (result === undefined || result === null) { + done([ + "error", + `function ${methodName} does not exist on control ${metadata.getElementName()}!`, + "none" + ]) } else { - // object, replacer function - // TODO: create usefull content from result - // result = JSON.stringify(result, window.wdi5.circularReplacer()); - - // check if of control to verify if the method result is a different control - if (result && result.getId && oControl.getId() !== result.getId()) { - // ui5 function like get parent might return another ui5 control -> return it to check with this wdi5 instance - result = window.wdi5.createControlId(result) - done(["success", result, "newElement"]) + // result mus be a primitive + if (window.wdi5.isPrimitive(result)) { + // getter + done(["success", result, "result"]) } else { - done([ - "success", - `instance of wdi5 representation of a ${metadata.getElementName()}`, - "element" - ]) + // object, replacer function + // TODO: create usefull content from result + // result = JSON.stringify(result, window.wdi5.circularReplacer()); + + // check if of control to verify if the method result is a different control + if (result && result.getId && oControl.getId() !== result.getId()) { + // ui5 function like get parent might return another ui5 control -> return it to check with this wdi5 instance + result = window.wdi5.createControlId(result) + done(["success", result, "newElement"]) + } else { + done([ + "success", + `instance of wdi5 representation of a ${metadata.getElementName()}`, + "element" + ]) + } } } } + }, + (error) => { + window.wdi5.Log.error("[browser wdi5] ERR: ", error) + done(["error", error.toString()]) } - }) + ) }, webElement, methodName, diff --git a/client-side-js/fireEvent.js b/client-side-js/fireEvent.js index 270fde2d..b978cbc5 100644 --- a/client-side-js/fireEvent.js +++ b/client-side-js/fireEvent.js @@ -1,29 +1,37 @@ async function clientSide_fireEvent(webElement, eventName, oOptions) { return await browser.executeAsync( (webElement, eventName, oOptions, done) => { - window.bridge.waitForUI5(window.wdi5.waitForUI5Options).then(() => { - window.wdi5.Log.info("[browser wdi5] working " + eventName + " for " + webElement) - // DOM to ui5 - let oControl = window.wdi5.getUI5CtlForWebObj(webElement) - if (oControl && oControl.hasListeners(eventName)) { - window.wdi5.Log.info("[browser wdi5] firing " + eventName + " on " + webElement) - // element existent and has the target event - try { - // eval the options indicated by option of type string - if (typeof oOptions === "string") { - oOptions = eval(oOptions)() + const errorHandling = () => { + window.wdi5.Log.error("[browser wdi5] couldn't find " + webElement) + done(["error", false]) + } + + window.wdi5.waitForUI5( + window.wdi5.waitForUI5Options, + () => { + window.wdi5.Log.info("[browser wdi5] working " + eventName + " for " + webElement) + // DOM to ui5 + let oControl = window.wdi5.getUI5CtlForWebObj(webElement) + if (oControl && oControl.hasListeners(eventName)) { + window.wdi5.Log.info("[browser wdi5] firing " + eventName + " on " + webElement) + // element existent and has the target event + try { + // eval the options indicated by option of type string + if (typeof oOptions === "string") { + oOptions = eval(oOptions)() + } + oControl.fireEvent(eventName, oOptions) + // convert to boolean + done(["success", true]) + } catch (e) { + done(["error", e.toString()]) } - oControl.fireEvent(eventName, oOptions) - // convert to boolean - done(["success", true]) - } catch (e) { - done(["error", e.toString()]) + } else { + errorHandling() } - } else { - window.wdi5.Log.error("[browser wdi5] couldn't find " + webElement) - done(["error", false]) - } - }) + }, + errorHandling + ) }, webElement, eventName, diff --git a/client-side-js/getControl.js b/client-side-js/getControl.js index e973fad3..73a216f3 100644 --- a/client-side-js/getControl.js +++ b/client-side-js/getControl.js @@ -1,31 +1,36 @@ async function clientSide_getControl(controlSelector) { controlSelector = await Promise.resolve(controlSelector) // to plug into fluent async api return await browser.executeAsync((controlSelector, done) => { + const errorHandling = (error) => { + window.wdi5.Log.error("[browser wdi5] ERR: ", error) + done(["error", error.toString()]) + } + const waitForUI5Options = Object.assign({}, window.wdi5.waitForUI5Options) if (controlSelector.timeout) { waitForUI5Options.timeout = controlSelector.timeout } - window.bridge - .waitForUI5(waitForUI5Options) - .then(() => { + window.wdi5.waitForUI5( + waitForUI5Options, + () => { window.wdi5.Log.info("[browser wdi5] locating " + JSON.stringify(controlSelector)) controlSelector.selector = window.wdi5.createMatcher(controlSelector.selector) - return window.bridge.findDOMElementByControlSelector(controlSelector) - }) - .then((domElement) => { - // window.wdi5.Log.info('[browser wdi5] control located! - Message: ' + JSON.stringify(domElement)); - // ui5 control - const ui5Control = window.wdi5.getUI5CtlForWebObj(domElement) - const id = ui5Control.getId() - window.wdi5.Log.info(`[browser wdi5] control with id: ${id} located!`) - const aProtoFunctions = window.wdi5.retrieveControlMethods(ui5Control) - // @type [String, String?, String, "Array of Strings"] - done(["success", domElement, id, aProtoFunctions]) - }) - .catch((error) => { - window.wdi5.Log.error("[browser wdi5] ERR: ", error) - done(["error", error.toString()]) - }) + window.bridge + .findDOMElementByControlSelector(controlSelector) + .then((domElement) => { + // window.wdi5.Log.info('[browser wdi5] control located! - Message: ' + JSON.stringify(domElement)); + // ui5 control + const ui5Control = window.wdi5.getUI5CtlForWebObj(domElement) + const id = ui5Control.getId() + window.wdi5.Log.info(`[browser wdi5] control with id: ${id} located!`) + const aProtoFunctions = window.wdi5.retrieveControlMethods(ui5Control) + // @type [String, String?, String, "Array of Strings"] + done(["success", domElement, id, aProtoFunctions]) + }) + .catch(errorHandling) + }, + errorHandling + ) }, controlSelector) } diff --git a/client-side-js/getSelectorForElement.js b/client-side-js/getSelectorForElement.js index 6c32066e..dfd1255c 100644 --- a/client-side-js/getSelectorForElement.js +++ b/client-side-js/getSelectorForElement.js @@ -1,21 +1,26 @@ async function clientSide_getSelectorForElement(oOptions) { return await browser.executeAsync((oOptions, done) => { - window.bridge - .waitForUI5(window.wdi5.waitForUI5Options) - .then(() => { + const errorHandling = (error) => { + window.wdi5.Log.error("[browser wdi5] ERR: ", error) + done(["error", error.toString()]) + return error + } + + window.wdi5.waitForUI5( + window.wdi5.waitForUI5Options, + () => { window.wdi5.Log.info("[browser wdi5] locating domElement") - return window.bridge.findControlSelectorByDOMElement(oOptions) - }) - .then((controlSelector) => { - window.wdi5.Log.info("[browser wdi5] controlLocator created!") - done(["success", controlSelector]) - return controlSelector - }) - .catch((error) => { - window.wdi5.Log.error("[browser wdi5] ERR: ", error) - done(["error", error.toString()]) - return error - }) + window.bridge + .findControlSelectorByDOMElement(oOptions) + .then((controlSelector) => { + window.wdi5.Log.info("[browser wdi5] controlLocator created!") + done(["success", controlSelector]) + return controlSelector + }) + .catch(errorHandling) + }, + errorHandling + ) }, oOptions) } diff --git a/client-side-js/injectUI5.js b/client-side-js/injectUI5.js index 648de3c8..93d26c1c 100644 --- a/client-side-js/injectUI5.js +++ b/client-side-js/injectUI5.js @@ -34,6 +34,22 @@ async function clientSide_injectUI5(config, waitForUI5Timeout) { window.wdi5.Log.info("[browser wdi5] injected!") }) + sap.ui.require(["sap/ui/test/autowaiter/_autoWaiterAsync"], (_autoWaiterAsync) => { + window.wdi5.waitForUI5 = function (oOptions, callback, errorCallback) { + oOptions = oOptions || {} + _autoWaiterAsync.extendConfig(oOptions) + + _autoWaiterAsync.waitAsync(function (sError) { + if (sError) { + errorCallback(new Error(sError)) + } else { + callback() + } + }) + } + window.wdi5.Log.info("[browser wdi5] window._autoWaiterAsync used in waitForUI5 function") + }) + // attach new bridge sap.ui.require(["sap/ui/test/RecordReplay"], (RecordReplay) => { window.bridge = RecordReplay diff --git a/client-side-js/interactWithControl.js b/client-side-js/interactWithControl.js index 07a23068..f9bd0aec 100644 --- a/client-side-js/interactWithControl.js +++ b/client-side-js/interactWithControl.js @@ -1,20 +1,25 @@ async function clientSide_interactWithControl(oOptions) { return await browser.executeAsync((oOptions, done) => { - window.bridge - .waitForUI5(window.wdi5.waitForUI5Options) - .then(() => { + const errorHandling = (error) => { + window.wdi5.Log.error("[browser wdi5] ERR: ", error) + done(["error", error.toString()]) + } + + window.wdi5.waitForUI5( + window.wdi5.waitForUI5Options, + () => { window.wdi5.Log.info("[browser wdi5] locating controlSelector") oOptions.selector = window.wdi5.createMatcher(oOptions.selector) - return window.bridge.interactWithControl(oOptions) - }) - .then((result) => { - window.wdi5.Log.info("[browser wdi5] interaction complete! - Message: " + result) - done(["success", result]) - }) - .catch((error) => { - window.wdi5.Log.error("[browser wdi5] ERR: ", error) - done(["error", error.toString()]) - }) + window.bridge + .interactWithControl(oOptions) + .then((result) => { + window.wdi5.Log.info("[browser wdi5] interaction complete! - Message: " + result) + done(["success", result]) + }) + .catch(errorHandling) + }, + errorHandling + ) }, oOptions) }