Skip to Content
Technical Articles
Author's profile photo Volker Buzek

Testing UI5 apps, part 2: Integration- aka OPA Testing

Testing UI5 apps

After starting off with Setup and Unit Testing in the first part of the blog series, let’s shift from the functional perspective to the integration aspect.

I’d like to think of integration tests as a means of making sure that -upon changes to the UI 5 application-, it continues to implement the most important business cases successfully. All that nice UX and UI boils down to the UI5 app fulfilling a purpose – and integration tests verify that the UI interaction in place allows for that to happen. Or as Arnaud Buchholz put it at UI5con 2018: “it checks the happy path” 🙂

In UI5-verse, integration tests are performed with “One Page Acceptance Tests” (or OPA(5) for brevity). It’s an extension of QUnit and operates the UI of the application just as a user would.

(repeat hint from Blog Part 1: the code for this blog series along with installation instructions is located at https://github.com/vobujs/openui5-sample-app-testing – yep, by the URL you can already tell that I copied the official sample ToDo UI5 app and modified it to fit the purpose of this blog series about testing.)

Bootstrapping manual Integration Tests

Similar to how Unit Tests are bootstrapped for manual browser testing, OPA Tests are started via a dedicated HTML file:

<!DOCTYPE html>
<html>
    <head>
        <title>OPA tests for Todo List</title>
        <meta http-equiv='X-UA-Compatible' content='IE=edge'>
        <meta charset="utf-8">

        <script id="sap-ui-bootstrap"
            src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
            data-sap-ui-theme="sap_belize"
            data-sap-ui-bindingSyntax="complex"
            data-sap-ui-compatVersion="edge"
            data-sap-ui-preload="async"
            data-sap-ui-resourceRoots='{"sap.ui.demo.todo": "../../"}'>
        </script>

        <script src="https://openui5.hana.ondemand.com/resources/sap/ui/thirdparty/qunit-2.js"></script>
        <script src="https://openui5.hana.ondemand.com/resources/sap/ui/qunit/qunit-2-css.js"></script>

        <script>
            QUnit.config.autostart = false;
            sap.ui.getCore().attachInit(function() {
                sap.ui.require([
                    "sap/ui/demo/todo/test/integration/AllJourneys"
                ], function() {
                    QUnit.start();
                });
            });
        </script>

    </head>
    <body>
        <div id="qunit"></div>
        <div id="qunit-fixture"></div>
    </body>
</html>

After UI5’s core has booted, OPA (in its QUnit-incarnation) is started and will execute AllJourneys.js: the container file for all Test-Journeys.

sap.ui.require([
  "sap/ui/test/Opa5"
  /* Pages */
  /* Journeys */
], function (Opa5) {
  Opa5.extendConfig({
    autoWait: true
    /* Default settings */
  });
});

Recommendation: turn on autoWait in default config. This excludes checking inactive or non-interactable Controls, so turn it off in specific (see below) waitFors (e.g. in test senarios when Message toasts are involved that only disappear after n number of seconds). But it will help in waiting for asynchronous operations on the UI to complete before test execution continues.

Tests are organized in “Journeys”, based on “Pages”:

├── AllJourneys.js
├── FilterJourney.js
├── SearchJourney.js
├── TodoListJourney.js
├── opaTests.qunit.html
└── pages
    ├── App.js
    └── Common.js

Journeys represent the use cases, Pages abstract UI elements and test their status and/or content.

Anatomy of OPA Journeys

The structure of an OPA Journey follows a basic pattern:
Given sets up the journey/test.
When gets the UI and environment into the state mandatory to test the business case.
Then performs the main assessment.

opaTest("description", function (Given, When, Then) {
  // Arrangements
  Given.iStartTheApp();

  // Actions
  When.onTheAppPage.iEnterTextAndPressEnter("some text");

  // Assertions
  Then.onTheAppPage.iShouldSeeTheText("some text")
    .and.iTeardownTheApp();
});

Heads up to possible Arrangement-/Given-methods:
it’s possible to start the UI5 app under test either in an iFrame (via iStartMyAppInAFrame) or by launching the Component only (via iStartMyUIComponent). The latter is significantly faster, easier to debug and also the “Fiori Launchpad way” of starting a UI5 app, but there’s advantages and disadvantages to both approaches. There’s also a cool blog post on a custom launcher, simulating starting an app from the FLP à la “iStartMyUIComponentInUshell”.

Similar to QUnit-based Unit Tests, OPA journeys can be grouped via QUnit.module("GroupName"):

QUnit.module("Todo List");
opaTest(...);
opaTest(...);
opaTest(...);

QUnit.module("Footer");
opaTest(...);
opaTest(...);

Anatomy of OPA pages

By convention, the actions and assertions of an OPA test are grouped in “page” files, corresponding to their UI5 view parts.

sap.ui.require([
    "sap/ui/test/Opa5"
], function (Opa5) {
    "use strict";
    Opa5.createPageObjects({
        onTheAppPage: {

            /* common methods outsourced in separate file "Common.js" */
            baseClass: Common,

            actions: {
                iEnterTextForNewItemAndPressEnter: function (text) {
                    return this.waitFor({
                        /* */
                    });
                }
            },

            assertions: {
                iShouldSeeTheItemBeingAdded: function (iItemCount, sLastAddedText) {
                    return this.waitFor({
                        /* */
                    });
                }
            }

        }
    });
});

As you can see from the coding, OPA (pages) make extensive use of sap.ui.test.Opa5.waitFor() – a method generating a Promise, used to synchronize application state with test state.
At the same time, this is the most important aspect of OPA testing to remember:

OPA tests need to be synchronized with the application.

sap.ui.test.Opa5.waitFor() is the code tool for that.

So no matter what UI element or interaction you want to test, it needs to go into a waitFor. The documentation has a solid list of how to retrieve and operate Controls, so here’s only an excerpt for some cases:

Finding a Control

  • by Id in a View:
byId: function(sId) {
    return this.waitFor({
        id: sId,
        viewName: "sap.ui.demo.todo.App",
        success : function (oControl) {
            /* assert sth on oControl */
        }
    })
}
withHelpOfMatchers: function (sText) {
    return this.waitFor({
        controlType: "sap.m.Button",
        viewName: "sap.ui.demo.todo.App",
        // require sap.ui.test.matchers.PropertyStrictEquals and
        // use as PropertyStrictEquals
        matchers: new PropertyStrictEquals({
            name: "text",
            value: sText
        }),
        success: function (oButton) {
            /* assert sth on oButton */
        }
    })
}
  • in a Dialog or Popup; attention: no finding by Id possible here, only by controlType:
inPopup: function (sControlTypeInPopup, sText) {
    return this.waitFor({
        controlType: sControlTypeInPopup,
        // require sap.ui.test.matchers.PropertyStrictEquals and
        // use as PropertyStrictEquals
        matchers: new PropertyStrictEquals({
            name: "text",
            value: sText
        }),
        searchOpenDialogs: true,
        success: function (oButton) {
            /* assert sth on oButton */
        }
    })
}

Interacting with a Control

Don’t fall back to using some variation of jQuery(oControl.getDomRef()).trigger(), but use the OPA-native sap.ui.test.actions instead. Again, the documentation has some good examples, so only note the basic code concept here:

iEnterTextForSearchAndPressEnter: function (sId, sViewName, sText) {
    return this.waitFor({
        id: sId,
        viewName: sViewName,
        // require sap.ui.test.actions.EnterText and
        // use as EnterText
        actions: [new EnterText({text: sText})],
        errorMessage: "The text cannot be entered"
    });
}

Useful OPA thingies to know

subsequent time-outs

If there’s some error in the asynchronous execution of the OPA test queue, it is very likely that subsequent tests (Page element functions) after the failed one error out too, even though they might work standalone.
This can happen due to the default timeout for sap.ui.test.Opa.config of 15 seconds: they might have eventually already been passed in the test case throwing the error. So all following tests run into the default timeout.

Don’t utilize Controls in success callback function

Again, OPA’s main feature is to sync test state with application state. If interactions are performed in the success handler of a waitFor, chances are that the application’s UI is off timing-wise and the interaction fails:

// will fail occasionally!
this.waitFor({
  id: "listItemId",
  // dependening on the processing power of the computer executing the test,
  // the list might not be entirely rendered and thus DOM operation on
  // list item nodes not available yet
  success: function (oListItem) {
    // double-bad: jQuery instead of sap.ui.test.actions.Press
    oListItem.$("bla").trigger("tap");  
    // ...
  }
}

It is more reliable to use waitFor‘s actions to interact with UI5 controls, along with autoWait:

this.waitFor({
  id: "listItemId",
  actions: new Press() // require: sap.ui.test.actions.Press as "Press",
  autoWait: true
}

Test for wording

Sure, the programming world is an English-speaking one, but quite often UI5 applications run in multi-language environments. So testing for English UI texts only is a bad idea:

// e.g. with a French system/browser, this will fail
matchers: [new PropertyStrictEquals({
  name: "text",
  value: iNumberItemsLeft + (iNumberItemsLeft === 1 ? " item left" : " items left")
})

Instead, use OPA’s I18NText-matcher: it will retrieve the correct text from the appropriate i18n-model for the browser runtime environment.

// ...
oItemLeftMatcher = new I18NText({
    propertyName: "text",
    key: "ITEM_LEFT"
}
// ...
// multi-lang match of label text
return this.waitFor({
    id: sItemsLeftLabelIdText,
    viewName: sViewName,
    matchers: oItemLeftMatcher,
    success: function () {
        Opa5.assert.ok(true, "" + iNumberItemsLeft + " items left");
    },
    errorMessage: "Items are not selected."
});

Debug application state

Given that OPA aka QUnit-extension queues up all Tests before executing them and waitFor additionally adds test-application synchronization, it’s hard to debug between the journey steps (Given-Arrangements, When-Actions and Then-Assertions).
So insert an empty waitFor to break into application runtime and test state:

opaTest("again: should unselect an item", function (Given, When, Then) {
        // Arrangements
        Given.iStartTheApp();

        //Actions
        When.onTheAppPage.iEnterTextForNewItemAndPressEnter("my test")
            .and.iSelectAllItems(true)
            .and.iClearTheCompletedItems();

        // debug the test and UI here!});
        When.waitFor({
            success: function() {
                debugger;
            }
        });

        When.onTheAppPage.iEnterTextForNewItemAndPressEnter("my test")
            .and.iSelectTheLastItem(true)
            .and.iSelectTheLastItem(false);

        // Assertions
        Then.onTheAppPage.iShouldSeeTheLastItemBeingCompleted(false).
        and.iTeardownTheApp();
    });

Conclusion

Integration- aka OPA-Testing helps to ensure successful business case execution upon changes to the UI5 application.

OPA Tests are organized in pages that are used in journeys to describe the UI interaction necessary for the business case.

On a technical level, sap.ui.test.Opa5.waitFor() is the programming means to sync test state and application state in order to avoid timing-based errors. Use its’ matchers, checks and actions extensively to find and interact with UI5 controls.

In the next blog post of the series, we’ll look at utilities that help with a broader test coverage.

Assigned Tags

      3 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Liem Dang
      Liem Dang

      Hey thank you for your explanation on integrationtests!
      I try to write also an opatest. In this test I have to deal with an uploadcollection (with a dialogfile) for uploading pdf´s.

      How does an integration test work there?

      Author's profile photo Dongcheng Wang
      Dongcheng Wang

      Hi Liem,

      I got the same OPA5 test requirement like yours, regarding file uploader and I've got no clue. just wondering if you have your solution worked? and if yes could you kindly share the key parts?

       

      Thanks a lot in advance,

      Dongcheng

      Author's profile photo Zoe Qin
      Zoe Qin

      I'm facing the same challenge here.