diff --git a/client-side-js/getControl.js b/client-side-js/getControl.js index 48750adf..3ca29772 100644 --- a/client-side-js/getControl.js +++ b/client-side-js/getControl.js @@ -22,10 +22,14 @@ async function clientSide_getControl(controlSelector) { // ui5 control const ui5Control = window.wdi5.getUI5CtlForWebObj(domElement) const id = ui5Control.getId() + const className = ui5Control.getMetadata()._sClassName 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: domElement, id: id, aProtoFunctions: aProtoFunctions }]) + done([ + "success", + { domElement: domElement, id: id, aProtoFunctions: aProtoFunctions, className: className } + ]) }) .catch(errorHandling) }, diff --git a/docs/recipes.md b/docs/recipes.md index f699ae8d..9e7ea715 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -235,3 +235,22 @@ it("get combobox single item aggregation as ui5 control", async () => { expect(await items.getTitle()).toEqual("Bahrain") }) ``` + +## use control info for analysis + +The control info object contains the following information: + +- id?: string // full UI5 control id as it is in DOM +- methods?: string[] // list of UI5 methods attached to wdi5 control +- className?: string // UI5 class name +- $?: Array // list of UwdioI5 methods attached to wdi5 control +- key?: string // wdio_ui_key + +```js +it("check the controlInfo for className", async () => { + const button = await browser.asControl(oButtonSelector) + const controlInfo = button.getControlInfo() // <-- + // can be accessed on the object + expect(controlInfo.className).toEqual("sap.m.Button") +}) +``` diff --git a/docs/ui5.md b/docs/ui5.md new file mode 100644 index 00000000..3994254c --- /dev/null +++ b/docs/ui5.md @@ -0,0 +1,243 @@ +# Functions + +## Example sap.m.Button + +```Json +{"methods":[ + "getText", +"setText", +"getType", +"setType", +"getWidth", +"setWidth", +"getEnabled", +"setEnabled", +"getIcon", +"setIcon", +"getIconFirst", +"setIconFirst", +"getActiveIcon", +"setActiveIcon", +"getIconDensityAware", +"setIconDensityAware", +"getTextDirection", +"setTextDirection", +"getAriaHasPopup", +"setAriaHasPopup", +"getAriaDescribedBy", +"addAriaDescribedBy", +"removeAriaDescribedBy", +"removeAllAriaDescribedBy", +"getAriaLabelledBy", +"addAriaLabelledBy", +"removeAriaLabelledBy", +"removeAllAriaLabelledBy", +"attachTap", +"detachTap", +"fireTap", +"attachPress", +"detachPress", +"firePress", +"getMetadata", +"useEnabledPropagator", +"setContextMenu", +"getContextMenu", +"initBadgeEnablement", +"updateBadgeValue", +"addCustomData", +"insertCustomData", +"getBadgeCustomData", +"getBadgeAnimationClass", +"removeBadgeCustomData", +"setBadgeAccentColor", +"setBadgePosition", +"updateBadgeVisibility", +"updateBadgeAnimation", +"badgeValueFormatter", +"setBadgeMinValue", +"setBadgeMaxValue", +"onBadgeUpdate", +"exit", +"ontouchstart", +"ontouchend", +"ontouchcancel", +"ontap", +"onkeydown", +"onkeyup", +"onfocusin", +"onfocusout", +"getPopupAnchorDomRef", +"getAccessibilityInfo", +"getBlocked", +"setBlocked", +"getBusy", +"setBusy", +"getBusyIndicatorDelay", +"setBusyIndicatorDelay", +"getBusyIndicatorSize", +"setBusyIndicatorSize", +"getVisible", +"setVisible", +"getFieldGroupIds", +"setFieldGroupIds", +"attachValidateFieldGroup", +"detachValidateFieldGroup", +"fireValidateFieldGroup", +"clone", +"addStyleClass", +"removeStyleClass", +"toggleStyleClass", +"hasStyleClass", +"isActive", +"invalidate", +"rerender", +"getDomRef", +"allowTextSelection", +"attachBrowserEvent", +"detachBrowserEvent", +"placeAt", +"onselectstart", +"getIdForLabel", +"destroy", +"isBusy", +"getControlsByFieldGroupId", +"checkFieldGroupIds", +"triggerValidateFieldGroup", +"getTooltip", +"setTooltip", +"destroyTooltip", +"getCustomData", +"removeCustomData", +"removeAllCustomData", +"indexOfCustomData", +"destroyCustomData", +"getLayoutData", +"setLayoutData", +"destroyLayoutData", +"getDependents", +"addDependent", +"insertDependent", +"removeDependent", +"removeAllDependents", +"indexOfDependent", +"destroyDependents", +"getDragDropConfig", +"addDragDropConfig", +"insertDragDropConfig", +"removeDragDropConfig", +"removeAllDragDropConfig", +"indexOfDragDropConfig", +"destroyDragDropConfig", +"register", +"deregister", +"getInterface", +"toString", +"prop", +"getUIArea", +"addDelegate", +"removeDelegate", +"addEventDelegate", +"removeEventDelegate", +"getFocusDomRef", +"focus", +"getFocusInfo", +"applyFocusInfo", +"getTooltip_AsString", +"getTooltip_Text", +"data", +"findElements", +"bindElement", +"unbindElement", +"getElementBinding", +"getDomRefForSetting", +"attachValidationSuccess", +"detachValidationSuccess", +"fireValidationSuccess", +"attachValidationError", +"detachValidationError", +"fireValidationError", +"attachParseError", +"detachParseError", +"fireParseError", +"attachFormatError", +"detachFormatError", +"fireFormatError", +"attachModelContextChange", +"detachModelContextChange", +"fireModelContextChange", +"applySettings", +"getId", +"setProperty", +"getProperty", +"validateProperty", +"isPropertyInitial", +"resetProperty", +"getOriginInfo", +"setAssociation", +"getAssociation", +"addAssociation", +"removeAssociation", +"removeAllAssociation", +"validateAggregation", +"setAggregation", +"indexOfAggregation", +"insertAggregation", +"addAggregation", +"removeAggregation", +"removeAllAggregation", +"destroyAggregation", +"isInvalidateSuppressed", +"setParent", +"getParent", +"isBinding", +"extractBindingInfo", +"getBindingInfo", +"bindObject", +"unbindObject", +"bindContext", +"unbindContext", +"bindProperty", +"unbindProperty", +"updateProperty", +"updateModelProperty", +"bindAggregation", +"unbindAggregation", +"updateAggregation", +"refreshAggregation", +"propagateMessages", +"isTreeBinding", +"updateBindings", +"isBound", +"getObjectBinding", +"getEventingParent", +"getBinding", +"getBindingPath", +"setBindingContext", +"setElementBindingContext", +"updateBindingContext", +"getBindingContext", +"setModel", +"addPropagationListener", +"removePropagationListener", +"getPropagationListeners", +"propagateProperties", +"getModel", +"getOwnModels", +"hasModel", +"findAggregatedObjects", +"onOwnerDeactivation", +"onOwnerActivation", +"isDestroyStarted", +"isDestroyed", +"attachEvent", +"attachEventOnce", +"detachEvent", +"hasListeners", +"isA", +"hasOwnProperty", +"isPrototypeOf", +"propertyIsEnumerable", +"valueOf", +"toLocaleString" +]} +``` diff --git a/docs/usage.md b/docs/usage.md index 4c762fae..6350c66c 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -422,3 +422,25 @@ In the test, you can navigate the UI5 webapp via `goTo(options)` in one of two w // or: await wdi5.goTo({ sHash: "#/Other" }) ``` + +## Control Info + +Control info is an object which can be retrieved from any wdi5 control by calling `getControlInfo()`. The control information object is loosely based on the UI5 metadata and also contains `className` and `id`. + +The list of attached wdio methods can be found in `$` and the UI5 control methods in the property `methods`. + +These properties can help to indentify the received control or test the control correctness. + +```js + const button = browser.asControl(oButtonSelector) + + const controlInfo = button.getControlInfo() // <-- + /* + * id?: string // full UI5 control id as it is in DOM + * methods?: string[] // list of UI5 methods attached to wdi5 control + * className?: string // UI5 class name + * $?: Array // list of UwdioI5 methods attached to wdi5 control + * key?: string // wdio_ui_key + */ +}) +``` diff --git a/docs/wdio.md b/docs/wdio.md new file mode 100644 index 00000000..b6b69933 --- /dev/null +++ b/docs/wdio.md @@ -0,0 +1,54 @@ +# Element + +## Function List + +```Json +"$", +"$$", +"addValue", +"clearValue", +"click", +"custom$$", +"custom$", +"doubleClick", +"dragAndDrop", +"getAttribute", +"getCSSProperty", +"getComputedLabel", +"getComputedRole", +"getHTML", +"getLocation", +"getProperty", +"getSize", +"getTagName", +"getText", +"getValue", +"isClickable", +"isDisplayed", +"isDisplayedInViewport", +"isEnabled", +"isEqual", +"isExisting", +"isFocused", +"isSelected", +"moveTo", +"nextElement", +"parentElement", +"previousElement", +"react$$", +"react$", +"saveScreenshot", +"scrollIntoView", +"selectByAttribute", +"selectByIndex", +"selectByVisibleText", +"setValue", +"shadow$$", +"shadow$", +"touchAction", +"waitForClickable", +"waitForDisplayed", +"waitForEnabled", +"waitForExist", +"waitUntil" +``` diff --git a/examples/ui5-js-app/webapp/test/e2e/basic.test.js b/examples/ui5-js-app/webapp/test/e2e/basic.test.js index c70e8c50..ae53413f 100644 --- a/examples/ui5-js-app/webapp/test/e2e/basic.test.js +++ b/examples/ui5-js-app/webapp/test/e2e/basic.test.js @@ -75,6 +75,20 @@ describe("ui5 basic", () => { expect(await browser.asControl(searchFieldSelector).getValue()).toEqual("search Value") }) */ + it("check the metadata", async () => { + const button = await browser.asControl({ + selector: { + id: "openDialogButton", + viewName: "test.Sample.view.Main" + } + }) + const metadata = button.getControlInfo() + + expect(metadata.id).toEqual("container-Sample---Main--openDialogButton") + expect(metadata.className).toEqual("sap.m.Button") + expect(metadata.key).toEqual("openDialogButtontestSample.view.Main") + }) + it("check getBinding returns a proper object", async () => { const title = await browser.asControl(titleSelector) const bindingInfo = await title.getBinding("text") diff --git a/examples/ui5-js-app/webapp/test/e2e/wdioBridge.test.js b/examples/ui5-js-app/webapp/test/e2e/wdioBridge.test.js index 141aea85..eebce1e4 100644 --- a/examples/ui5-js-app/webapp/test/e2e/wdioBridge.test.js +++ b/examples/ui5-js-app/webapp/test/e2e/wdioBridge.test.js @@ -1,5 +1,3 @@ -const { it } = require("mocha") -const { Logger } = require("../../../../../dist/lib/Logger") const Main = require("./pageObjects/Main") const titleSelector = { diff --git a/src/lib/wdi5-control.ts b/src/lib/wdi5-control.ts index 885648d7..241d6e9a 100644 --- a/src/lib/wdi5-control.ts +++ b/src/lib/wdi5-control.ts @@ -5,11 +5,10 @@ import { clientSide_interactWithControl } from "../../client-side-js/interactWit import { clientSide_executeControlMethod } from "../../client-side-js/executeControlMethod" import { clientSide_getAggregation } from "../../client-side-js/_getAggregation" import { clientSide_fireEvent } from "../../client-side-js/fireEvent" - +import { wdi5ControlMetadata, wdi5Selector } from "../types/wdi5.types" import { Logger as _Logger } from "./Logger" -const Logger = _Logger.getInstance() -import { wdi5Selector } from "../types/wdi5.types" +const Logger = _Logger.getInstance() /** * This is a bridge object to use from selector to UI5 control, @@ -17,8 +16,13 @@ import { wdi5Selector } from "../types/wdi5.types" */ export class WDI5Control { _controlSelector: wdi5Selector = null - _webElement: WebdriverIO.Element | string = null + // return value of Webdriver interface: JSON web token + _webElement: WebdriverIO.Element | string = null // TODO: type "org.openqa.selenium.WebElement" + // wdio elment retrieved separately via $() _webdriverRepresentation: WebdriverIO.Element = null + _metadata: wdi5ControlMetadata = {} + + // TODO: move to _metadata _wdio_ui5_key: string = null _generatedUI5Methods: Array _initialisation = false @@ -49,6 +53,8 @@ export class WDI5Control { this.attachControlBridge(this._generatedUI5Methods as Array) this.attachWdioControlBridge(this._generatedWdioMethods as Array) + this.setControlInfo() + // set the succesful init param this._initialisation = true @@ -74,6 +80,8 @@ export class WDI5Control { this.attachControlBridge(this._generatedUI5Methods as Array) this.attachWdioControlBridge(this._generatedWdioMethods as Array) + this.setControlInfo() + // set the succesful init param this._initialisation = true } @@ -88,6 +96,27 @@ export class WDI5Control { return this._initialisation } + getControlInfo(): wdi5ControlMetadata { + return this._metadata + } + + setControlInfo( + metadata: wdi5ControlMetadata = { + key: this._wdio_ui5_key, + $: this._generatedWdioMethods, + methods: this._generatedUI5Methods, + id: this._domId + } + ) { + this._metadata.$ = metadata.$ ? metadata.$ : this._metadata.$ + this._metadata.id = metadata.id ? metadata.id : this._metadata.id + this._metadata.methods = metadata.methods ? metadata.methods : this._metadata.methods + this._metadata.className = metadata.className ? metadata.className : this._metadata.className + this._metadata.key = metadata.key ? metadata.key : this._metadata.key + + return this._metadata + } + /** * @return {WebdriverIO.Element} the webdriver Element */ @@ -450,14 +479,18 @@ export class WDI5Control { } const _result = await clientSide_getControl(controlSelector) - const { domElement, id, aProtoFunctions } = _result[1] + const { domElement, id, aProtoFunctions, className } = _result[1] const result = _result[0] + // TODO: move to constructor? // save the webdriver representation by control id if (result) { // only if the result is valid this._webdriverRepresentation = await $(`//*[@id="${id}"]`) this._generatedWdioMethods = this._retrieveControlMethods(this._webdriverRepresentation) + + // add metadata + this._metadata.className = className this._domId = id } diff --git a/src/types/wdi5.types.ts b/src/types/wdi5.types.ts index cc1750d7..f1a99409 100644 --- a/src/types/wdi5.types.ts +++ b/src/types/wdi5.types.ts @@ -110,6 +110,17 @@ export interface wdi5Selector { selector: wdi5ControlSelector } +/** + * + */ +export interface wdi5ControlMetadata { + id?: string // full UI5 control id as it is in DOM + methods?: string[] // list of UI5 methods attached to wdi5 control + className?: string // UI5 class name + $?: Array // list of UwdioI5 methods attached to wdi5 control + key?: string // wdio_ui_key +} + // yet unused export interface wdi5Bridge extends Window { bridge: RecordReplay