Dieser Blog soll einen Überblick bieten, wie dein Code mit WDI5 getestet werden kann.
Was ist WDI5?
WDI5 ist die Abkürzung für „WebdriverIO for SAPUI5“. Es ist ein Framework, das für die Automatisierung von End-to-End-Tests von SAPUI5-Anwendungen entwickelt wurde. Es nutzt den WebdriverIO-Testrunner als Basis und erweitert diesen um spezielle Funktionalitäten und Tools für die Testautomatisierung von SAPUI5-Anwendungen, wie z. B. die Überprüfung von UI5-Komponenten, das Abrufen von Daten aus dem UI5-Modell und das Ausführen von UI5-Aktionen. WDI5 ermöglicht es Entwicklern, ihre Anwendungen auf Konsistenz, Integrität und Fehlerfreiheit zu testen, indem sie eine Reihe von Testschritten automatisch ausführen, die von der Navigation bis zur Überprüfung von Daten und Benutzeraktionen reichen.
Zusammenfassend ermöglicht WDI5 eine effiziente und einfache Automatisierung von End-to-End-Tests für SAPUI5-Anwendungen, die eine schnellere und zuverlässigere Veröffentlichung sicherstellen.
Ein Bestandteil von WDI5 ist das Service-Module „WDIO-UI5-Service“. Dieses stellt die speziellen UI5-spezifischen Funktionalitäten zur Verfügung. Es wurde deshalb implementiert, dass auch ohne das komplette WDI5 Framework zu nutzen SAPUI5 Applikationen getestet werden können. Je nachdem, ob eine Browserbasierte Laufzeitumgebung oder eine Hybrid-App-Umgebung getestet werden soll, setzt man entweder den „WDIO-UI5-Service“ oder das Framework WDI5 ein.
Im folgenden Blog konzentrieren wir uns auf eine browserbasierte Umgebung und verwenden deshalb nur das Service Modul.
Installation
Hier muss unterschieden werden, ob wir WDI5 als Framework verwenden wollen oder nur das Service-Module.
Da WDI5 bzw. WDIO-UI5-Service auf Node.js basieren, muss zuallererst sichergestellt sein, das Node.js installiert ist. https://nodejs.org/en/download/
Ist das erledigt, navigieren wir in der Konsole in unseren Projektordner.
Um unsere App auszuführen, benötigen wir einen Webserver. Da wir sich das UI5-Tooling in den letzten Jahren bewährt hat, empfiehlt es sich dieses zu verwenden. Dafür führen wir in den Befehl „npm install --save-dev @ui5/cli“ aus.
Darauffolgend installieren wir unsere Testumgebung.
Da WDI5 derzeit noch nicht mit der neusten WDIO-Version (WDIO 8) unterstützt wird, müssen wir den letzten Release der WDIO v7 installieren. Das machen wir mit dem Befehl „npm install @wdio/cli@^7“
Mit dem Befehl „npx wdio“ starten wir nach der Installation danach den Configuration Helper und wählen folgende Optionen:
Nach der Installation der benötigten Abhängigkeiten, wird die Datei wdio.conf.js Datei erstellt, In dieser müssen wir noch einen Konfigurationsblock hinzufügen.
exports.config = {
wdi5: {
screenshotPath: "./__screenshots__",//[optional] {string}, default:""
screenshotsDisabled: false, //[optional] default: false;
logLevel: "silent", //[optional] error|verbose|silent,default: "error"
skipInjectUI5OnStart: false, //[optional] default: false; true
waitForUI5Timeout: 15000 //[optional] default: 15000
},
// …
capabilities: [{
maxInstances: 5,
browserName: 'chrome', "goog:chromeOptions": {
args: ["--headless", "--disable-gpu", "--window-size=1930,1090"]
},
acceptInsecureCerts: true
}],
// …
};
Der wdi5 Block steuert die Grundeinstellungen des WDI5 Services. Der screenshotPath legt fest an welchem Ort die erstellten Screenshots abgelegt werden. Der Parameter screenshotsDisabled steuert ob überhaupt welche erstellt werden. Der waitForUI5Timeout Parameter steuert die maximale Wartezeit, um auf UI5-bezogene Vorgänge innerhalb einer UI5-App zu warten (waitFor() funktionalität in OPA5).
Im capabilities Block können wir noch die Headless Funktionalität und die Browserfenstergröße festlegen.
Haben wir alle vorherigen Schritte soweit ausgeführt, ist die Testumgebung soweit bereit.
Wie bereits beschrieben werden die Tests in den *.test.js Dateien definiert.
Deren Aufbau ist recht simpel und sieht wie folgt aus:
const Main = require("../pageObjects/Main");
describe('My Tests', () => {
before(async () => {
await Main.open();
});
beforeEach(async () => {
await browser.screenshot("list-interaction-before");
});
afterEach(async () => {
await browser.screenshot("list-interaction-after");
});
it("should have the correct list header", async () => {
const list = await browser.asControl({
selector: {
controlType: "sap.m.Table",
searchOpenDialogs: false
},
forceSelect: force
});
const list = await Main.Page.getList();
expect(await list.getProperty("header")).toEqual("Categories");
});
Mit dem ersten Befehl werden die benötigten Seitenelemente importiert. Hier in dem Fall brauchen wir lediglich die Main-Page. Danach sollte immer ein describe-Block Pro Testfile folgen. Empfehlenswert ist hier auch die gleiche Benennung wie das File selbst. Damit ist sichergestellt, dass bei einem fehlgeschlagenen Test direkt ersichtbar ist, wo der Fehler lokalisiert ist.
Jeder describe-Block kann mit mehreren sogenannten Hooks (before, after, beforeEach, afterEach) ausgestattet werden. In unserem Fall nutzen wir „before“ um die Applikation zu initialisieren. Und die beforeEach und afterEach, um jeweils einen Screenshot des aktuellen Zustands der App zu dokumentieren.
Darauf folgen die eigentlichen Tesblöcke diese starten immer mit dem Schlüsselwort „it“ und einem sprechenden Text, der den Test so gut wie möglich beschreibt.
Mit dem Schlüsselwort await wird sichergestellt, dass die Funktion vollständig durchlaufen wurde, bevor zum nächsten Schritt übergegangen wird.
Selektor
Im obigen Beispiel wird mit dem Ausdruck browser.asControl ein UI5 Control gesucht, welches über den mitgegebenen Selektor beschrieben wird. Hier suchen wir eine sap.m.Table die sich nicht in einem Dialog befindet. Bei genauerer Betrachtung stellt man fest, dass die Notation der Selektoren gleich ist wie bei den OPA5 Selektoren. Das kommt daher, dass die OPA5 Selektoren im WDI5 Framework übernommen wurden. Mit dem Schlüsselwort forceSelect (Standard: false) legen wir fest, ob das Control gecached wird oder nicht. Wird es auf true gesetzt, wird das Element nochmals aus dem Browserkontext abgerufen, was nützlich sein kann, wenn sich Bestandteile des Elements geändert haben, oder wenn es zerstört und neu erstellt wurde.
Um die eigentliche Teststruktur etwas übersichtlicher und wiederverwendbar zu gestalten, empfiehlt es sich die eigentliche Logik in die jeweiligen Page Dateien auszulagern. Im obigen Beispiel wurde die Suche nach der gerade beschriebenen Liste ebenfalls in dem Page Objekt über die Methode getList umgesetzt.
async getList(force = true, inDialog = false) {
// const test2 = sap.fe.test;
const listSelector = {
selector: {
controlType: "sap.m.Table",
interaction: "root",
searchOpenDialogs: inDialog
},
forceSelect: force
};
return await browser.asControl(listSelector);
}
Für eine noch dynamischere Verwendung ist es hier auch möglich Parameter für den Selektor zu definieren. In diesem Beispiel kann eine sap.m.Table gesucht werden, die auf der Hauptseite oder auch in einem Dialog befindet und es wird definiert, ob das Control gecached wird oder nicht.
Mit dem Schlagwort „interaction“ kann die Suche noch weiter verfeinert werden. Einige SAPUI5 Controls bestehen auf DOM ebene aus weiteren Elementen. Ein Beispiel hierfür wäre ein Searchfield. Dieses besteht zum einen aus dem Suchfeld selbst, aus einem Inputfeld, einem Resetund einem Search Button.
An diesem Beispiel kann gut veranschaulicht werden, wie der „interaction“ -Parameter verwendet werden kann.
root: das Root-Element des Steuerelements (Searchfield)
focus: das Element, das normalerweise den Fokus erhält (Inputfield)
press: das Element, welches das press Ereignis erhält (Searchbutton)
auto: das Element, das Ereignisse empfängt. Es sucht nach speziellen Elementen mit folgender Priorität: press, focus, root. (Searchbutton)
Aktionen
Nachdem wir uns das gewünschte Control beschafft haben, können wir verschiedene Sachen machen. Zum einen können wir Actions über das Control ausführen oder wir überprüfen es, nachdem wir eine Aktion ausgeführt haben, auf eine bestimmte Eigenschaft.
Als Aktionen stehen uns ähnlich wie bei OPA5 das Press Event (await button.press()), die EnterText Methodik (await input.enterText("some Text")) sowie die Navigation (wdi5.goTo(path)) zur Verfügung. Darüber hinaus haben wir durch die asControl-Funktion das UI5 Control selbst in der Testumgebung zur Verfügung und können damit auch alle Events und Funktionen die das Control besitzt ausführen (await list.getItems(1)).
Die Navigation kann entweder über den einen Hash (await wdi5.goTo("#/Other")) oder über eine in der SAPUI5 App definierte Route angegeben werden.
await browser.goTo({
sComponentId: "Sample",
sName: "Main"
})
Assertions
Die Assertions, also die Zustandsüberprüfungen, die grundsätzlich nach dem Ausführen der Aktionen stehen wurden bei WDI5 von WDIO übernommen. Diese wiederum basieren auf den Jest Matcher und erweitern diese um ein paar weitere Funktionalitäten.
Die am häufigsten verwendeten werden hier im Folgenden aufgelistet.
expect(content.length).toBe(3)
expect(await list.getId()).toContain("List")
expect(await Item.getProperty("title")).toEqual(await Item.getTitle())
expect(webButton).toBeTruthy()
expect(await input.getVisible()).toBe(true)
expect(appointments1.length).toBeGreaterThan(4)
expect(appointments1.length).toBeLessThan(4)
expect(ours).toMatch(/.*someText/)
Alle dieser aufgelisteten Assertions können mit dem Zusatz „.not.“ negiert werden:
expect(content.length).not.toBe(3)
expect(await list.getId()).not.toContain("List")
expect(await listItems.length)).not.toEqual(16)
Darstellung nach und während der Testdurchführung
Um die geschriebenen Tests auszuführen müssen 2 Schritte ausgeführt werden.
Zuallererst starten wir unseren Webserver, der unsere App zur Verfügung stellt. Wie oben bereits beschrieben verwenden wir ui5-Toolling und geben in der Konsole einfach „ui5 serve“ ein.
Zum Starten der Testumgebung können wir entweder den Befehl „node_modules/.bin/wdio run wdio.conf.js“ ausführen, oder wir erstellen in der Datei package.json einen Skript Befehl
"scripts": {
"test": "wdio run wdio.conf.js"
},
Standardmäßig ist wird der Spec-Reporter mit WDIO mit installiert. Dessen Ausgabe folgendermaßen aussieht:
Tipps und Tricks bei Unit-Tests
Mock Daten
Beim Testen einer Applikation solltest du immer Versuchen mit Mock Daten zu arbeiten. Somit kannst du eine mögliche Fehlerquelle beim Testdurchlauf eliminieren. Daten im Backendsystem können sich jederzeit ändern und deine Assertions fehlschlagen lassen. Deine Mock Daten sollten in aller Regel Stabil bleiben. Das erreichst du bei WDI5 am einfachsten, wenn du die UI5-Tooling Middleware „sap-fe-mockserver“ und binden diese in unsere ui5.yaml ein.
- name: sap-fe-mockserver
beforeMiddleware: csp
configuration:
mountPath: /
debug: true
watch: true
annotations:
- localPath: './webapp/annotations/annotation.xml'
urlPath: '/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Annotations*'
services:
- urlPath: '/V2/Northwind/Northwind.svc'
metadataXmlPath: './webapp/localService/metadata.xml'
mockdataRootPath: './webapp/localService/data'
generateMockData: false
Debuggen
Das Debugging kann bei WDI5 unter Verwendung von VS-Code recht einfach realisiert werden. Dazu setzen wir an der Stelle, an der wir halten möchten einfach einen Breakpoint indem wir links neben der Zeilennummer klicken.
Starten wir nun die Test über ein in der package.json definiertes Skript, wird der Test an der markierten Stelle anhalten und wir können den aktuellen Zustand der Applikation untersuchen.
Weitere nützliche Tipps findet ihr unter: