SuperAPI AI Directory

ui5-testing

Testing & quality

View repository

Updated 10-16 02:37

About this skill

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  # interactive setup wizard

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", {
      // wdi5 options
      waitForUI5Timeout: 15000
    }]
  ],
  baseUrl: "http://localhost:4004",
  mochaOpts: {
    timeout: 60000
  }
});

Writing wdi5 tests

// test/e2e/orders.test.js
import { wdi5 } from "wdi5";

describe("Orders", () => {
  before(async () => {
    await browser.url("/orders/webapp/index.html");
  });

  it("displays the orders list", async () => {
    // Select UI5 controls by viewName + id
    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({
      selector: {
        viewName: "my.app.view.OrdersObjectPage",
        id: "headerTitle"
      }
    });
    expect(await oObjectPageTitle.getProperty("text")).toBeTruthy();
  });
});

OPA5 — integration testing

OPA5 runs inside the browser and interacts with UI5 controls directly.

Page object pattern (recommended)

// test/integration/pages/OrdersListPage.js
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", length: nCount }),
            success: () => Opa5.assert.ok(true, `${nCount} orders visible`),
            errorMessage: `Expected ${nCount} orders in table`
          });
        }
      }
    }
  });
});

OPA5 journey

// test/integration/OrdersJourney.js
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

// test/unit/formatter/StatusFormatter.js
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