| name | ui5-testing |
| description | Use when working with SAPUI5 testing: wdi5 WebdriverIO E2E test, OPA5 integration test, QUnit unit test, wdio-ui5-service, browser.byControl selector, page object pattern, UIVeri5 migration, UI5 test automation, wdio.conf.js ui5 service, jasmine mocha test runner.
|
| metadata | {"category":"ui5","version":"1.0.0","keywords":["wdi5","WebdriverIO","wdio-ui5-service","OPA5","QUnit","E2E test","browser.byControl","page object","UIVeri5 migration","integration test"],"related":{"testing":"CAP backend integration tests alongside UI5 tests","ui5-data-binding":"test OData V4 binding behavior end-to-end","fiori-elements-floorplans":"E2E test Fiori Elements pages"}} |
UI5 Testing — Best Practices
Primary reference: https://ui5.sap.com/#/topic/7cdee404cac441888539ed7bfe076e57
wdi5: https://ui5-community.github.io/wdi5/
wdi5 npm: https://www.npmjs.com/package/wdio-ui5-service
OPA5: https://ui5.sap.com/#/topic/2696ab50faad458f9b4027ec2f9b884d
Testing strategy
| Layer | Tool | What |
|---|
| Unit | QUnit + sinon | Individual functions, formatters, controllers |
| Integration | OPA5 | UI flows within the app, page-level interactions |
| End-to-end | wdi5 + WebdriverIO | Full scenarios across multiple apps, real browser |
UIVeri5 is deprecated — migrate to wdi5 for all E2E testing.
wdi5 — E2E testing (recommended)
wdi5 is the successor to UIVeri5. It uses WebdriverIO and provides UI5-specific selector APIs.
Setup
npm install --save-dev @wdio/cli @wdio/local-runner @wdio/mocha-framework wdio-ui5-service
npx wdio config
wdio.conf.js:
import { defineConfig } from "@wdio/config";
export const config = defineConfig({
runner: "local",
specs: ["./test/e2e/**/*.test.js"],
capabilities: [{
browserName: "chrome"
}],
framework: "mocha",
services: [
["ui5", {
waitForUI5Timeout: 15000
}]
],
baseUrl: "http://localhost:4004",
mochaOpts: {
timeout: 60000
}
});
Writing wdi5 tests
import { wdi5 } from "wdi5";
describe("Orders", () => {
before(async () => {
await browser.url("/orders/webapp/index.html");
});
it("displays the orders list", async () => {
const oTable = await browser.byControl({
selector: {
viewName: "my.app.view.OrdersList",
id: "ordersTable"
}
});
expect(await oTable.isDisplayed()).toBe(true);
});
it("navigates to order detail", async () => {
const oFirstRow = await browser.byControl({
selector: {
viewName: "my.app.view.OrdersList",
controlType: "sap.m.ColumnListItem",
ancestor: { id: "ordersTable" }
}
});
await oFirstRow.press();
const oObjectPageTitle = await browser.byControl({
: {
: ,
:
}
});
( oObjectPageTitle.()).();
});
});
OPA5 — integration testing
OPA5 runs inside the browser and interacts with UI5 controls directly.
Page object pattern (recommended)
sap.ui.define([
"sap/ui/test/Opa5",
"sap/ui/test/actions/Press",
"sap/ui/test/matchers/AggregationLengthEquals"
], (Opa5, Press, AggregationLengthEquals) => {
Opa5.createPageObjects({
onTheOrdersList: {
actions: {
iPressTheFirstOrder() {
return this.waitFor({
id: "ordersTable",
viewName: "my.app.view.OrdersList",
matchers: new AggregationLengthEquals({ name: "items", length: 1 }),
actions: new Press(),
errorMessage: "No order found in the table"
});
}
},
assertions: {
iSeeOrdersInTheTable(nCount) {
return this.waitFor({
id: "ordersTable",
viewName: "my.app.view.OrdersList",
matchers: new AggregationLengthEquals({ name: "items", : nCount }),
: ..(, ),
:
});
}
}
}
});
});
OPA5 journey
sap.ui.define([
"sap/ui/test/opaQunit",
"./pages/OrdersListPage"
], (opaTest) => {
QUnit.module("Orders");
opaTest("Should see the orders list", (Given, When, Then) => {
Given.iStartMyApp();
Then.onTheOrdersList.iSeeOrdersInTheTable(5);
Then.iTeardownMyApp();
});
opaTest("Should navigate to order detail", (Given, When, Then) => {
Given.iStartMyApp();
When.onTheOrdersList.iPressTheFirstOrder();
Then.onTheOrdersDetail.iSeeTheOrderHeader();
Then.iTeardownMyApp();
});
});
QUnit — unit testing
sap.ui.define([
"sap/ui/qunit/utils/createAndAppendDiv",
"my/app/formatter/StatusFormatter"
], (createAndAppendDiv, StatusFormatter) => {
QUnit.module("StatusFormatter");
QUnit.test("should return correct criticality for Approved status", (assert) => {
const result = StatusFormatter.getCriticality("Approved");
assert.strictEqual(result, 3, "Approved returns criticality 3 (green)");
});
QUnit.test("should return 1 for Rejected status", (assert) => {
const result = StatusFormatter.getCriticality("Rejected");
assert.strictEqual(result, 1, "Rejected returns criticality 1 (red)");
});
});
npm test scripts
{
"scripts": {
"test": "npm run test:unit && npm run test:e2e",
"test:unit": "ui5 test --spec test/unit",
"test:e2e": "wdio run wdio.conf.js",
"test:e2e:watch": "wdio run wdio.conf.js --watch"
}
}
Common mistakes to avoid
-
❌ Using UIVeri5 — deprecated, no longer maintained
-
✅ Migrate to wdi5 + WebdriverIO for all E2E tests
-
❌ Selecting controls by DOM ID in wdi5 — fragile, breaks with UI5 updates
-
✅ Always select by viewName + UI5 id or controlType using the wdi5 selector API
-
❌ Writing OPA5 tests without page objects — hard to maintain
-
✅ Always use the page object pattern — one page object per view
-
❌ Skipping unit tests for formatters and helper functions
-
✅ QUnit is fast — unit test all logic that doesn't require the DOM
-
❌ Running E2E tests against production — test data bleeds in
-
✅ Always run E2E against a dedicated test instance with test data