Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e76d09f
fix: performance
Siolto Feb 11, 2025
c8c2955
fix: improve error handling and result processing in executeControlMe…
Siolto Feb 12, 2025
9537088
fix: refactor executeObjectMethod not using callbacks
Siolto Feb 12, 2025
68a1ec4
test: update DateTimePicker API test to use ISO string format
Siolto Feb 12, 2025
64b94a0
fix: update generated-methods test to handle different date formats b…
Siolto Feb 12, 2025
9b511c4
fix: await browser.asControl in press test for proper asynchronous ha…
Siolto Feb 12, 2025
594c704
fix: bidi race condition in retrieveElements
Siolto Feb 12, 2025
c61d284
fix: update Planning Calendar test to handle BIDI-specific DOM controls
Siolto Feb 12, 2025
a47ea66
fix: optimize control retrieval for BIDI by removing unnecessary await
Siolto Feb 12, 2025
0635b06
fix: adjust BIDI handling in exec test to avoid Promise.all for perfo…
Siolto Feb 12, 2025
c28aaec
chore: configuration
Siolto Feb 12, 2025
657f6d6
fix: add TODO comment for handling UI5 injection options
Siolto Feb 12, 2025
d8a7dc8
refactor: remove propietary waitForUI5
Siolto Apr 10, 2025
60292bf
refactor: run planningCalendar test also with BiDi protocol
Siolto Apr 17, 2025
f0a9c7e
fix: improve peformance while retrieving multiple elements (#665)
Siolto May 16, 2025
83787c0
refactor: rename clientSide__checkForUI5Ready to clientSide_checkForU…
vobu May 21, 2025
8609d21
chore: lock file
vobu May 21, 2025
2dcee32
fix: test
vobu May 21, 2025
4d957db
refactor: rm unnecessary await
vobu May 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions client-side-js/_checkForUI5Ready.cjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
async function clientSide__checkForUI5Ready(browserInstance) {
async function clientSide_checkForUI5Ready(browserInstance) {
return await browserInstance.execute(async () => {
try {
await window.wdi5.waitForUI5(window.wdi5.waitForUI5Options)
await window.bridge.waitForUI5(window.wdi5.waitForUI5Options)
} catch (error) {
return window.wdi5.errorHandling(error)
}
Expand All @@ -11,5 +11,5 @@ async function clientSide__checkForUI5Ready(browserInstance) {
}

module.exports = {
clientSide__checkForUI5Ready
clientSide_checkForUI5Ready
}
4 changes: 2 additions & 2 deletions client-side-js/allControls.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ async function clientSide_allControls(controlSelector, browserInstance) {
}

try {
await window.wdi5.waitForUI5(waitForUI5Options)
await window.bridge.waitForUI5(waitForUI5Options)
} catch (error) {
return window.wdi5.errorHandling(error)
}

window.wdi5.Log.info("[browser wdi5] locating " + JSON.stringify(controlSelector))
controlSelector.selector = window.wdi5.createMatcher(controlSelector.selector)
let domElements;
let domElements

try {
domElements = await window.bridge.findAllDOMElementsByControlSelector(controlSelector)
Expand Down
209 changes: 103 additions & 106 deletions client-side-js/executeControlMethod.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,121 +10,118 @@ if (global.browser) {
* @param {WebdriverIO.Browser} browserInstance
* @param {Object} args proxied arguments to UI5 control method at runtime
*/
async function executeControlMethod(webElement, methodName, browserInstance, args) {
return await browserInstance.execute(
function executeControlMethod(webElement, methodName, browserInstance, args) {
return browserInstance.execute(
async (webElement, methodName, args) => {
return new Promise(async (resolve, reject) => {
await window.wdi5.waitForUI5(
window.wdi5.waitForUI5Options,
() => {
// DOM to UI5
const oControl = window.wdi5.getUI5CtlForWebObj(webElement)
try {
await window.bridge.waitForUI5(window.wdi5.waitForUI5Options)

// execute the function
let result = oControl[methodName].apply(oControl, args)
const metadata = oControl.getMetadata()
// DOM to UI5
const oControl = window.wdi5.getUI5CtlForWebObj(webElement)

if (Array.isArray(result)) {
if (result.length === 0) {
resolve({ status: 0, result: result, returnType: "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
// execute the function
let result = oControl[methodName].apply(oControl, args)
const metadata = oControl.getMetadata()

result = window.wdi5.createControlIdMap(result, controlType)
resolve({ status: 0, result: result, returnType: "aggregation" })
} else {
resolve({ status: 0, result: result, returnType: "result" })
if (Array.isArray(result)) {
if (result.length === 0) {
return { status: 0, result: result, returnType: "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

result = window.wdi5.createControlIdMap(result, controlType)
return { status: 0, result: result, returnType: "aggregation" }
} else {
return { status: 0, result: result, returnType: "result" }
}
} else {
// ui5 api <control>.focus() doesn't have return value
if (methodName === "focus" && result === undefined) {
return {
status: 0,
result: `called focus() on wdi5 representation of a ${metadata.getElementName()}`,
returnType: "element"
}
} else if (methodName === "exec" && result && result.status > 0) {
return {
status: result.status,
message: result.message
}
} else if (result === undefined || result === null) {
return {
status: 1,
result: `function ${methodName} does not exist on control ${metadata.getElementName()}!`,
returnType: "none"
}
} else {
if (window.wdi5.isPrimitive(result)) {
return { status: 0, result: result, returnType: "result" }
} else if (
// we have an object that is not a UI5 control
typeof result === "object" &&
result !== null &&
!(result instanceof sap.ui.core.Control) &&
!(result instanceof sap.ui.core.Item)
) {
// save before manipulate
const uuid = window.wdi5.saveObject(result)

// FIXME: extract, collapse and remove cylic in 1 step
// extract the methods first
const aProtoFunctions = window.wdi5.retrieveControlMethods(result, true)

// flatten the prototype so we have all funcs available
const collapsed = window.wdi5.collapseObject(result)
// exclude cyclic references
const collapsedAndNonCyclic = JSON.parse(
JSON.stringify(collapsed, window.wdi5.getCircularReplacer())
)
// remove all empty Array elements, inlcuding private keys (starting with "_")
const semanticCleanedElements = window.wdi5.removeEmptyElements(collapsedAndNonCyclic)

return {
status: 0,
object: semanticCleanedElements,
returnType: "object",
aProtoFunctions: aProtoFunctions,
uuid: uuid,
nonCircularResultObject: semanticCleanedElements
}
} else if (
typeof result === "object" &&
result !== null &&
// wdi5 returns a wdi5 control if the UI5 api return its control
// allows method chaining
!(result instanceof sap.ui.base.Object)
) {
return {
status: 2,
returnType: "unknown"
}
} else {
// ui5 api <control>.focus() doesn't have return value
if (methodName === "focus" && result === undefined) {
resolve({
// we got ourselves a regular UI5 control
// check that we're not working against ourselves :)
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)
return { status: 0, result: result, returnType: "newElement" }
} else {
return {
status: 0,
result: `called focus() on wdi5 representation of a ${metadata.getElementName()}`,
result: `instance of wdi5 representation of a ${metadata.getElementName()}`,
returnType: "element"
})
} else if (methodName === "exec" && result && result.status > 0) {
resolve({
status: result.status,
message: result.message
})
} else if (result === undefined || result === null) {
resolve({
status: 1,
result: `function ${methodName} does not exist on control ${metadata.getElementName()}!`,
returnType: "none"
})
} else {
if (window.wdi5.isPrimitive(result)) {
resolve({ status: 0, result: result, returnType: "result" })
} else if (
// we have an object that is not a UI5 control
typeof result === "object" &&
result !== null &&
!(result instanceof sap.ui.core.Control) &&
!(result instanceof sap.ui.core.Item)
) {
// save before manipulate
const uuid = window.wdi5.saveObject(result)

// FIXME: extract, collapse and remove cylic in 1 step
// extract the methods first
const aProtoFunctions = window.wdi5.retrieveControlMethods(result, true)

// flatten the prototype so we have all funcs available
const collapsed = window.wdi5.collapseObject(result)
// exclude cyclic references
const collapsedAndNonCyclic = JSON.parse(
JSON.stringify(collapsed, window.wdi5.getCircularReplacer())
)
// remove all empty Array elements, inlcuding private keys (starting with "_")
const semanticCleanedElements =
window.wdi5.removeEmptyElements(collapsedAndNonCyclic)

resolve({
status: 0,
object: semanticCleanedElements,
returnType: "object",
aProtoFunctions: aProtoFunctions,
uuid: uuid,
nonCircularResultObject: semanticCleanedElements
})
} else if (
typeof result === "object" &&
result !== null &&
// wdi5 returns a wdi5 control if the UI5 api return its control
// allows method chaining
!(result instanceof sap.ui.base.Object)
) {
resolve({
status: 2,
returnType: "unknown"
})
} else {
// we got ourselves a regular UI5 control
// check that we're not working against ourselves :)
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)
resolve({ status: 0, result: result, returnType: "newElement" })
} else {
resolve({
status: 0,
result: `instance of wdi5 representation of a ${metadata.getElementName()}`,
returnType: "element"
})
}
}
}
}
},
window.wdi5.errorHandling.bind(this, null, reject)
)
})
}
}
} catch (error) {
window.wdi5.errorHandling.bind(error)
}
},
webElement,
methodName,
Expand All @@ -146,14 +143,14 @@ async function clientSide_executeControlMethod(webElement, methodName, browserIn
* **/
let result
try {
result = await executeControlMethod(webElement, methodName, browserInstance, args)
result = executeControlMethod(webElement, methodName, browserInstance, args)
} catch (err) {
// devtools and webdriver protocol don't share the same error message
if (err.message?.includes("is stale") || err.message?.includes("stale element reference")) {
logger.debug(`webElement ${JSON.stringify(webElement)} stale, trying to renew reference...`)
let renewedWebElement = await wdi5Control.renewWebElementReference()
if (renewedWebElement) {
result = await executeControlMethod(renewedWebElement, methodName, browserInstance, args)
result = executeControlMethod(renewedWebElement, methodName, browserInstance, args)
logger.debug(`successfully renewed reference: ${JSON.stringify(renewedWebElement)}`)
} else {
logger.error(`failed renewing reference for webElement: ${JSON.stringify(webElement)}`)
Expand Down
Loading
Loading