Skip to content

Commit

Permalink
refactor: better error message output and smaller bugfixes (#338)
Browse files Browse the repository at this point in the history
closes #318,
paves the way toward completing #331 
* fix: catch getWebElement error
* fix: improve fluent async api error messages and behaviour
* feat: throw an error if the aggregation could not be found
* fix: resolve browserInstance before receiving the aggregation in the browser scope
* fix: rephrase error messages
* feat: add error logging tests
* refactor: remove duplicated comment from error-logging test
* fix: make the fluentAsync error consistent over the different node versions
Co-authored-by: Volker Buzek <[email protected]>
  • Loading branch information
Siolto authored Sep 8, 2022
1 parent a9accba commit 05f05f0
Show file tree
Hide file tree
Showing 7 changed files with 319 additions and 74 deletions.
1 change: 1 addition & 0 deletions client-side-js/_getAggregation.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
async function clientSide_getAggregation(webElement, aggregationName, browserInstance) {
webElement = await Promise.resolve(webElement) // to plug into fluent async api
browserInstance = await Promise.resolve(browserInstance)
return await browserInstance.executeAsync(
(webElement, aggregationName, done) => {
window.bridge.waitForUI5(window.wdi5.waitForUI5Options).then(() => {
Expand Down
4 changes: 4 additions & 0 deletions client-side-js/injectUI5.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,10 +284,14 @@ async function clientSide_injectUI5(config, waitForUI5Timeout, browserInstance)
/**
* creates a array of objects containing their id as a property
* @param {[sap.ui.core.Control]} aControls
* @throws {Error} error if the aggregation was not found that has to be catched
* @return {Array} Object
*/
window.wdi5.createControlIdMap = (aControls, controlType = "") => {
// the array of UI5 controls need to be mapped (remove circular reference)
if (!aControls) {
throw new Error("Aggregation was not found!")
}
return aControls.map((element) => {
// just use the absolute ID of the control
if (controlType === "sap.m.ComboBox" && element.data("InputWithSuggestionsListItem")) {
Expand Down
7 changes: 4 additions & 3 deletions examples/ui5-js-app/e2e-test-config/wdio.base.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ const { join } = require("path")
exports.baseConfig = {
wdi5: {
screenshotPath: join("webapp", "test", "__screenshots__"),
logLevel: "error"
logLevel: "error",
waitForUI5Timeout: 29000
},
maxInstances: 10,
capabilities: [
Expand All @@ -25,7 +26,7 @@ exports.baseConfig = {
bail: 0,
baseUrl: "http://localhost:8888",

waitforTimeout: 10000,
waitforTimeout: 20000,
connectionRetryTimeout: process.argv.indexOf("--debug") > -1 ? 1200000 : 120000,
connectionRetryCount: 3,

Expand All @@ -34,7 +35,7 @@ exports.baseConfig = {
framework: "mocha",
mochaOpts: {
ui: "bdd",
timeout: process.argv.indexOf("--debug") > -1 ? 600000 : 60000
timeout: process.argv.indexOf("--debug") > -1 ? 600000 : 90000
},
reporters: ["spec"]
}
194 changes: 194 additions & 0 deletions examples/ui5-js-app/webapp/test/e2e/error-logging.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
const { afterEach } = require("mocha")
const sinon = require("sinon")

const expectDefaultErrorMessages = (wdi5Control) => {
expect(console.red.called).toBeTruthy()
expect(
console.red
.getCall(0)
.calledWith(
"[wdi5]",
`call of _getControl() failed because of: Error: No DOM element found using the control selector ${JSON.stringify(
wdi5Control._controlSelector.selector
)}`
)
).toBeTruthy() // dirty but otherwise we have no access to the selector
expect(
console.red.getCall(1).calledWith("[wdi5]", `error retrieving control: ${wdi5Control._wdio_ui5_key}`)
).toBeTruthy() // dirty but otherwise we have no acces to the key
}

/**
* test the error logs of the wdi5 logger when controls cannot be found
* for every test we are using a slightly different selector so we don't have
* to use "foceSelect: true" all the time
*/
describe("Error logging", () => {
const sandbox = sinon.createSandbox()

beforeEach(() => {
sandbox.spy(console, "red")
})

afterEach(() => {
sandbox.restore()
})

it("should log the correct error messages when control was not found", async () => {
const selectorWithWrongId = {
selector: {
id: "wrongId"
}
}
const wdi5ControlWithWrongId = await browser.asControl(selectorWithWrongId)

expectDefaultErrorMessages(wdi5ControlWithWrongId)
expect(console.red.callCount).toEqual(2)
})

it("should log the correct error messages when 'press' is executed on an not found control; WITHOUT fluent async api", async () => {
const selectorWithWrongId = {
selector: {
id: "wrongIdWithPress"
}
}
const wdi5ControlWithWrongId = await browser.asControl(selectorWithWrongId)
await wdi5ControlWithWrongId.press()

expectDefaultErrorMessages(wdi5ControlWithWrongId)
expect(console.red.callCount).toEqual(3)
expect(
console.red.getCall(2).calledWith("[wdi5]", `cannot call press(), because control could not be found`)
).toBeTruthy()
})

it("should log the correct error messages when 'press' is executed on an not found control; WITH fluent async api", async () => {
const selectorWithWrongId = {
selector: {
id: "wrongIdWithPressFluentAsync"
}
}

const wdi5ControlWithWrongId = await browser.asControl(selectorWithWrongId).press()

expectDefaultErrorMessages(wdi5ControlWithWrongId)
expect(console.red.callCount).toEqual(3)
expect(
console.red.getCall(2).calledWith("[wdi5]", `cannot call press(), because control could not be found`)
).toBeTruthy()
})

it("should log the correct error messages when 'getAggregation' is executed on an not found control; WITHOUT fluent async api", async () => {
const selectorWithWrongId = {
selector: {
id: "wrongIdWithGetAggregation"
}
}

const wdi5ControlWithWrongId = await browser.asControl(selectorWithWrongId)
await wdi5ControlWithWrongId.getAggregation("items")

expectDefaultErrorMessages(wdi5ControlWithWrongId)

expect(console.red.callCount).toEqual(3)
expect(
console.red
.getCall(2)
.calledWith("[wdi5]", `cannot get aggregation "items", because control could not be found`)
).toBeTruthy()
})
it("should log the correct error messages when 'getAggregation' is executed on an not found control; WITH fluent async api", async () => {
const selectorWithWrongId = {
selector: {
id: "wrongIdWithGetAggregationFluentAsync"
}
}
await browser.asControl(selectorWithWrongId).getAggregation("items")
// we need the wdi5 control for the assertions. As we are not using forceSelect
// there should be no additional error messages
const wdi5ControlWithWrongId = await browser.asControl(selectorWithWrongId)

expectDefaultErrorMessages(wdi5ControlWithWrongId)
expect(console.red.callCount).toEqual(3)
expect(
console.red
.getCall(2)
.calledWith("[wdi5]", `cannot get aggregation "items", because control could not be found`)
).toBeTruthy()
})

it("should log the correct error messages when 'getAggregation' is executed on an control with a wrong aggregation; WITH fluent async api", async () => {
const selectorWithWrongId = {
selector: {
id: "container-Sample---Main--NavFwdButton"
}
}

await browser.asControl(selectorWithWrongId).getAggregation("tooltip")

expect(console.red.callCount).toEqual(1)
expect(
console.red
.getCall(0)
.calledWith("[wdi5]", `call of _getAggregation() failed because of: Error: Aggregation was not found!`)
).toBeTruthy()
})

it("should log the correct error messages when 'enterText' is executed on an not found control; WITHOUT fluent async api", async () => {
const selectorWithWrongId = {
selector: {
id: "wrongIdWithEnterText"
}
}

const wdi5ControlWithWrongId = await browser.asControl(selectorWithWrongId)
await wdi5ControlWithWrongId.enterText("test")

expectDefaultErrorMessages(wdi5ControlWithWrongId)

expect(console.red.callCount).toEqual(3)
expect(
console.red.getCall(2).calledWith("[wdi5]", `cannot call enterText(), because control could not be found`)
).toBeTruthy()
})
// TODO: introudce fluent async api for enterText
// it("should log the correct error messages when 'enterText' is executed on an not found control WITH fluent async api", async () => {
// const selectorWithWrongId = {
// selector: {
// id: "wrongIdWithEnterTextFluentAsync"
// }
// }

// const wdi5ControlWithWrongId = await (await browser.asControl(selectorWithWrongId)).enterText()

// expectDefaultErrorMessages(wdi5ControlWithWrongId)

// expect(console.red.callCount).toEqual(3)
// expect(console.red.getCall(2).calledWith("[wdi5]", `cannot call enterText(), because control could not be found`)).toBeTruthy()
// });

it("should log the correct error messages when multiple functions are executed on an control where an error in the queue occurrs; WITH fluent async api", async () => {
const selectorWithWrongId = {
selector: {
id: "container-Sample---Main--NavFwdButton"
}
}

await browser.asControl(selectorWithWrongId).getWrongFunction().getSecondWrongFunction()

expect(console.red.callCount).toEqual(2)
expect(
console.red
.getCall(0)
.calledWith(
"[wdi5]",
`One of the calls in the queue "getWrongFunction().getSecondWrongFunction()" previously failed!`
)
).toBeTruthy()
expect(
console.red
.getCall(1)
.calledWith("[wdi5]", `Cannot read property 'getSecondWrongFunction' in the execution queue!`)
).toBeTruthy()
})
})
3 changes: 3 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 28 additions & 11 deletions src/lib/wdi5-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,20 +355,37 @@ export async function _addWdi5Commands(browserInstance: WebdriverIO.Browser) {
functionQueue.push(prop)
return asyncMethods.includes(prop)
? (...boundArgs) => makeFluent(promise[prop](...boundArgs))
: makeFluent(promise.then((object) => object[prop]))
: makeFluent(
promise.then((object) => {
// when object is undefined the previous function call failed
try {
return object[prop]
} catch (error) {
// different node versions return a different `error.message` so we use our own message
Logger.error(`Cannot read property '${prop}' in the execution queue!`)
}
})
)
},
apply(_, thisArg, boundArgs) {
return makeFluent(
// When "targetFunction" is empty we can assume that the ui5 control was not found
promise.then((targetFunction) =>
targetFunction
? Reflect.apply(targetFunction, thisArg, boundArgs)
: Logger.error(
`Can not call "${functionQueue.filter(
(name) => !asyncMethods.includes(name)
)}", because control could not be found`
)
)
// When "targetFunction" is empty we can assume that there are errors in the execution queue
promise.then((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")) {
functionQueue.splice(functionQueue.indexOf("then"))
Logger.error(
`One of the calls in the queue "${functionQueue.join(
"()."
)}()" previously failed!`
)
}
}
})
)
}
}
Expand Down
Loading

0 comments on commit 05f05f0

Please sign in to comment.