Skip to content

Latest commit

 

History

History
336 lines (255 loc) · 11.9 KB

README.md

File metadata and controls

336 lines (255 loc) · 11.9 KB

wdi5 wdi5 Logo

wdi5 (/vdif5/) is a wrapper around appium-driven Webdriver.IO-tests, utilizing UI5’s test API.

It is designed to run cross-platform, executing OPA5-/UIveri5-style integration tests on a UI5 application - in the browser, in a hybrid (cordova) container or as an Electron application.

npm (scoped) npm (prod) dependency version npm (prod) dependency version

wdi5 = UI5 Test API + Webdriver.IO + appium

demo testing iOS + browser in parallel

Table of contents

about

wdi5 comes in two flavours:

  • npm wdio-ui5-service: a browser-based plugin to Webdriver.IO
  • npm wdi5: an extension to Webdriver.IO, using appium to communicate with the hybrid app on iOS, Android and Electron. The wdi5-extension contains wdio-ui5-service, allowing for both browser-based and hybrid-app-testing.

wdio-ui5-service allows for a lightweight setup if test scope is on the browser. As to where the wdi5-extension gives you the full "app-testing-package".

Installation + Setup

Specific to the desired test-scope, please see:

  • brower-based "Webdriver.IO"-plugin: wdio-ui5-service
  • hybrid app extension: wdi5 (includes all browser-based capabilities)

Usage

Run-(Test-)Time usage of wdi5 is agnostic to its' test-scope (browser or native) and centers around the global browser-object, be it in the browser or on a real mobile device.

Test runs are always started via the regular webdriver.io-cli:

$> npx wdio

Control selectors

The entry point to retrieve a control is always browser.asControl(oSelector).

oSelector re-uses the OPA5 control selectors, supplemented by the optional wdio_ui5_key and forceSelect properties.

wdio-ui5 stores control references internally in order to save browser roundtrip time on repeatedly using a control across different test cases. For that, wdio-ui5 computes unique identifiers for controls - with wdio_ui5_key, you can assign such an ID manually if required.

The forceSelcet (default: false) property can be set to true to force wdio-ui5 to again retrieve the control from the browser context and update the internally stored reference as well as available control method.

The forceSelcet option also updates the wdio control reference each time a mehtod is executed on a wdi5 control.

const oSelector = {
  wdio_ui5_key: 'wdio-ui5-button', // optional unique internal key to map and find a control
  forceSelect: true, // forces the test framework to again retrieve the control from the browser context
  selector: {
    // sap.ui.test.RecordReplay.ControlSelector
    id: 'UI5control_ID',
    viewName: 'your.namespace.App'
  }
};
const control = browser.asControl(oSelector);
// now use one of the below API methods on <control>

These are the supported selectors from sap.ui.test.RecordReplay.ControlSelector:

selector supported in wdi5
id
viewName
controlType
bindingPath
I18NText -
Anchestor -
labelFor -
properties
const bindingPathSelector = {
  selector: {
    // sap.ui.test.RecordReplay.ControlSelector
    bindingPath: {
      propertyPath: "/Customers('TRAIH')/ContactName"
    },
    properties: {
      value: 'Helvetius Nagy'
    },
    viewName: 'test.Sample.view.Main',
    controlType: 'sap.m.Input'
  }
};
const control = browser.asControl(bindingPathSelector);
// now use one of the below API methods on `control`

Locating a UI5 control via a regular expression works as well:

// select a Button with Id ending in "MyButton"
// across all views in the UI5 app
const crossViewById = {
  selector: {
    id: /.*MyButton$/
  }
};
expect(browser.asControl(crossViewById).getVisible()).toBeTruthy();

wdio-ui5 supports method chaining, so you can do:

browser.asControl(selector).getText().getId().setProperty('title', 'new title');

In case you are not able to create an explicit selector for a control, but you are able to find it via any webdriver strategy, you can use the getSelectorForElement method. It returns a selector which can subsequently be used in asControl:

const webdriverLocatorSelector = {
  selector: browser.getSelectorForElement({
    domElement: $('/xpath/to/button'),
    settings: {preferViewId: true}
  })
};
const control = browser.asControl(webdriverLocatorSelector);
// now use any of the UI5 native controls' API methods on `control`

API methods

all UI5 control's native methods

Once the control is retrieved in a test, use any of the native UI5 control's methods on it.

This is possible because of a runtime proxy wdio-ui5 provides that transistions the UI5 control's methods from browser- to Node.js-runtime.

# terminal 1: run webapp on port 8888
$> npx soerver -d <path/to/webapp> -p 8888

# terminal 2: run test
$> npx wdio run <path/to/conf> --spec <path/to/test>

Execution of 1 spec files started at 2020-08-24T15:49:54.625Z
# ...

# breakpoint is hit after retrieving a control
# in the test via "browser.asControl(buttonSelector)"

# snippet of output of "Object.getOwnPropertyNames(ui5Button)"
length: 220
[
  // ...
  "extractBindingInfo",
  "findAggregatedObjects",
  "findElements",
  "fireEvent",
  "fireFormatError",
  "fireModelContextChange",
  "fireParseError",
  "firePress",
  "fireTap",
  "fireValidateFieldGroup",
  "fireValidationError",
  "fireValidationSuccess",
  "focus",
  // ...
  "getBinding",
  "getBindingContext",
  "getBindingInfo",
  "getBindingPath",
  "getBlocked",
  "getBusy",
  "getBusyIndicatorDelay",
  "getBusyIndicatorSize",
  "getContextMenu",
  "getControlsByFieldGroupId",
  "getCustomData",
  "getDependents",
  "getDomRef",
  "getDomRefForSetting",
  "getDragDropConfig",
  // ...
  "getLayoutData",
  "getModel",
  "getObjectBinding",
  "getOriginInfo",
  "getParent",
  "getPopupAnchorDomRef",
  "getPropagationListeners",
  "getProperty",
  "getText",
  "getTextDirection",
  "getTooltip",
  "getTooltip_AsString",
  "getTooltip_Text",
  "getType",
  "getUIArea",
  "getVisible",
  "getWidth",
  "hasListeners",
  // ...
]

This method bridge does not proxy private control methods (starting with _), getAggregation (and getMetadata) though. getAggregation (see below) is provided by wdi5 separately with a UI5-compatible API signature.

goTo

When navigating the User-Agent manually, please use wdi5's own browser.goTo('#/path/to/route') implementation rather than Webdriver.IO's browser.url(). This makes sure that the UI5 test API is loaded and runtime.

getAggregation

getAggregation(sAggregationName) => wdi5Controls[]: retrieve the elements of aggregation sAggregationName of a control getAggregation

const ui5ListItems = browser.asControl(oListSelector).getAggregation('items');
ui5ListItems.forEach((listItem) => {
  expect(listItem.getTitle()).not.toBe('');
});

get$Shorthand conveniences

If getAggregation is called via a shorthand such as sap.m.ListBase.getItems(), additional convenience functions are available:

get$Shorthand(true) (e.g. getItems(true)): if true retrieves the aggregation as webdriver elements only (not as UI5 controls!). This is a huge performance improvement in case you don't need all elements of the aggregation as fully qualified UI5 controls.

get$Shorthand(2) (e.g. getItems(2)): if set as Number, the result array contains a single UI5 control from the aggregation at index Number (here: 2). This is a huge performance improvement in case you dont need all controls of the aggegation as fully qualified UI5 controls, but rather one specific single control.

enterText

enterText(sText) => this {wdi5Control}: input sText into a (input-capable) control via EnterText

browser.asControl(inputSelector).enterText('new Text');

Function mock for event handler

If an item has a custom attribute defined (eg. data:key="exampleKey") which is needed in the event handler function, the access of the data() function to retrieve the key can be done by specifying an eval property as (object) argument to the event handler.

Can be accessed in standard UI5 manner oEvent.getParameter("listItem").data("key").

// use in wdio-ui5 test
// example for the [sap.m.List](https://sapui5.hana.ondemand.com/#/api/sap.m.ListBase%23events/itemPress) event `itemPress`
browser.asControl(listSelector).fireEvent('itemPress', {
  eval: () => {
    return {
      listItem: {
        data: () => {
          return 'account.relationships';
        }
      }
    };
  }
});

Assertions

Recommendation is to use the Webdriver.IO-native extension to JEST's expect and matchers as described in https://webdriver.io/docs/assertion.html.

Screenshots

At any point in your test(s), you can screenshot the current state of the UI via browser.screenshot():

it('...', () => {
  // ...
  browser.screenshot('some-id');
  // ...
});

This puts a png into the configured wdi5.screenshotPath (from wdio.conf.js).

The file name is prepended with a date indicator (M-d-hh-mm-ss), holds screenshot in the filename and is appended with the id you provide (here: some-id). Example: 5-5-17-46-47-screenshot--some-id.png

Logger

For convenient console output, use wdi5().getLogger(). It supports the syslog-like levels log,info, warn and error:

require('wdi5').getLogger().log('any', 'number', 'of', 'log', 'parts');

The log level is set by the either in wdio.conf.js via wdi5.logLevel or by wdi5().getLogger().setLoglevel(level = {String} error | verbose | silent)

FAQ/hints

wdi5 tests itself with wdi5 - see the test/- and test/ui5-app/test/e2e/ directory for sample wdio.conf.js-files and sample tests.

Run yarn test for wdi5 testing itself 😊

Collaboration

  • yarn
  • prettier
  • commitlint

License

This work is dual-licensed under Apache 2.0 and the Derived Beer-ware 🍺 License. The official license will be Apache 2.0 but finally you can choose between one of them if you use this work.

Thus, when you like this stuff, buy @vobu or @The_dominiK a beer when you see them.