in this blog post I want to show you a not pretty new but still quite unknown tool, that eases the process of writing good and stable OPA5 tests dramatically. The Fiori Elements Test Library V4.
The idea behind the test library was to reduce the efforts of writing and maintaining your OPA5 tests. As Fiori Elements applications are mostly generated and follow specific patterns in their control structure, a test library was developed to localize these patterns and cover the most common user interactions and assertions.
With that we can simplify our journeys, make them more readable, reduce the chance of code repetition and use the same actions and assertions across multiple applications. Another big benefit of this library is that it is kept up to date when framework changes happen. Maybe the internal structure of a control changes and the pattern for identifying your control changes as well, the tests with the library still run successfully.
Naming of test functions
The test library has the following predefined test functions:
- Base arrangements that control the general lifecycle of the app
- Base actions and assertions that can be used across the whole app, e.g. for Dialogs
- Actions and Assertions for the ListReport, ObjectPage and Shell
These test functions have a consistent naming pattern which they follow to also improve the readability of the tests.
- Actions:
i<DoSth><WithSth>
- Assertions:
i<ExpectSth>
- Consistent parameter signature
- String for identifying UI elements via label on UI element, e.g.
iExecuteAction("myAction")
- Current state of UI elements, e.g.
iCheckEdit({visible: true})
- String for identifying UI elements via label on UI element, e.g.
- Supports chaining of functions on the same hierarchy, e.g.
onFilterBar()
i<DoSth><WithSth>.and.i<DoSth><WithSth>
i<ExpectSth>.and.i<ExpectSth>
Integration
The general folder structure of the OPA5 tests stays almost the same:
test
│
├───integration # By convention OPA tests are added to the test folder inside a UI5 app.
│ ├───pages # The pages folder contains the page objects for the different
│ │ ├───MainListReport.js # Fiori Elements templates.
│ │ └───MainObjectPage.js # Can be extended with own actions & assertions.
│ ├───Opa.qunit.js # Launch URL, pages and configuration settings can be set here.
│ │ # Uses JourneyRunner to call journeys.
│ ├───OpaJourney.js # A journey contains the actual tests which consists of steps
│ │ # constructed from the page objects.
│ └───opaTests.qunit.html # Sets up the UI5 environment to start the OPA5 tests.
│
├───flpSandbox.html # Starts the app with mock data.
├───testsuite.qunit.html # Testsuite that contains all unit and integration tests.
└───testsuite.qunit.js
- The opaTests.qunit.html is the file you execute to run your tests. Inside the .html file, the JavaScript file Opa.qunit.js is referenced.
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{appTitle}}</title>
<meta charset="UTF-8">
<script id="sap-ui-bootstrap"
src="../../resources/sap-ui-core.js"
data-sap-ui-theme='sap_fiori_3'
data-sap-ui-resourceroots='{
"sap.fe.demo.app": "../../"
}'
data-sap-ui-animation="false"
data-sap-ui-compatVersion="edge"
data-sap-ui-async="true">
</script>
<link rel="stylesheet" type="text/css" href="../../resources/sap/ui/thirdparty/qunit-2.css">
<script src="../../resources/sap/ui/thirdparty/qunit-2.js"></script>
<script src="../../resources/sap/ui/qunit/qunit-junit.js"></script>
<script src="Opa.qunit.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
</body>
</html>
- In the Opa.qunit.js file we reference the so called JourneyRunner to which we can pass the test configuration, the journeys that should be executed, and the page objects for the ListReport and ObjectPage which will provide the exact test functions of the test library. The Journey Runner will then run our OPA tests.
sap.ui.require([
"sap/fe/test/JourneyRunner",
"sap/fe/test/Shell",
"sap/fe/demo/app/test/integration/pages/MainListReport",
"sap/fe/demo/app/test/integration/pages/MainObjectPage",
"sap/fe/demo/app/test/integration/OpaJourney"
],
function (JourneyRunner, Shell, MainListReport, MainObjectPage, Journey) {
'use strict';
var JourneyRunner = new JourneyRunner({
// start flpSandbox.html in test folder
launchUrl: sap.ui.require.toUrl('incidents') + '/test/flpSandbox.html',
opaConfig: { timeout: 30 }
});
JourneyRunner.run(
{
pages: { onTheMainPage: MainListReport, onTheDetailPage: MainObjectPage }
},
Journey.run,
);
}
)
- In our page objects we include and configure the actual library. sap.fe.test.ListReport for the list report and sap.fe.test.ObjectPage for the object page. The relevant information can be found in the manifest.json. If there are user interactions or assertions that can not be covered by the available standard functions e.g. you have some custom coding, you can still write your own functions here.
sap.ui.define(['sap/fe/test/ListReport', "sap/ui/test/Opa5"], function (ListReport, Opa5) {
'use strict';
var AdditionalCustomListReportDefinition = {
// custom actions and assertions can be defined here
actions: {},
assertions: {
iShouldSeeMyCustomActionEnabled: function () {
return this.waitFor({
id: "myCustomSection"
properties: {
enabled: true
},
success: function () {
Opa5.assert.ok(true, "The custom section is visible and enabled")
},
errorMessage: "The custom section could not be found or is not enabled!"
})
}
}
};
return new ListReport(
{
appId: 'sap.fe.demo.app', // Compare sap.app.id in manifest.json
componentId: 'AppList', // Compare sap.ui5.routing.targets.<ListReport|ObjectPage>.id in manifest.json
entitySet: 'EntitySet' // Compare sap.ui5.routing.targets.<ListReport|ObjectPage>.options.settings.entitySet in manifest.json
},
AdditionalCustomListReportDefinition
);
});
Usage
To use now the test library we can write normal Journeys that live, by convention, in a run function. In the API documentation you can have a look which generic actions and assertions are available for each individual page. Especially at the beginning, it can be a little cumbersome to navigate through the API, find the correct actions/assertions and the arguments you can pass to them.
A common scenario you can cover with the library could be to search for some specific entry in the table of the list report. To identify the correct functions in the documentation you can follow the following pattern/questions:
Do I want to execute/assert something on the ListReport, ObjectPage or Shell?
Do I want to execute an action(When.***) or assertion(Then***) on something?
Which method in the actions could match the part of the view I want to interact with?
FilterBar: We have to click on the link that is provided in the Description Column
Which actions can fulfill my scenario?
iChangeSearchField and iExecuteSearch
After identifying the relevant functions, we can now write our journey file the same way we identified the relevant functions in the API
sap.ui.define(['sap/ui/test/opaQunit'], function (opaTest) {
'use strict';
var Journey = {
run: function () {
QUnit.module('Sample journey');
opaTest('#0: Start', function (Given, When, Then) {
Given.iResetTestData().and.iStartMyApp("incidents-tile");
Then.onTheMainPage.iSeeThisPage();
});
opaTest('#1: ListReport: Check List Report Page loads', function (Given, When, Then) {
When.onTheMainPage.onFilterBar().iChangeSearchField("incident_001");
When.onTheMainPage.onFilterBar().iExecuteSearch();
// we can also chain multiple functions on the same hierarchy
// When.onTheMainPage.onFilterBar().iChangeSearchField("incident_001).and.iExecuteSearch()
// assert that there should be only 4 rows displayed
Then.onTheMainPage.onTable().iCheckRows(4);
// custom assertion
Then.onTheMainPage.iShouldSeeMyCustomActionEnabled()
});
opaTest('#999: Tear down', function (Given, When, Then) {
Given.iTearDownMyApp();
});
}
};
return Journey;
});
wdi5 integration
With the help of some colleagues from SAP (thanks Nicolas LUNET for the implementation and Georg Bischoff for the documentation!) we also have an integration of the test library in wdi5 as well! You can have a look at the detailed documentation how you can integrate the library in wdi5. With that achieved we can more or less just copy & paste the same code you used in your Integration test for your E2E test! Isn’t that nice?!
//... wdi5
it("I search product 'Office Plant'", async () => {
await FioriElementsFacade.execute((Given, When, Then) => {
// use the same code in the "Facade" as in your OPA5 tests
When.onTheMainPage.onFilterBar().iChangeSearchField("Office Plant").and.iExecuteSearch()
Then.onTheMainPage.onTable().iCheckRows(3)
})
})
Conclusion
The Fiori Elements Test Library V4 is a useful tool, that eases the process of writing and maintaining your test code. If you want to learn more about the test library itself, it’s usage or just want to try it out yourself have a look at the already existing tutorial for the test library. If you’re already using the test library, let me know in the comments what your experiences with the library have been so far.