Repeat hint throughout the blog series: code -along with installation instructions- is at https://github.com/vobu/openui5-sample-app-testing.
git clone
,npm install
,grunt serve
-> http://localhost:8080 and code/follow along.
Age hint: this blog series started whenui5-tooling
was still in SAP's womb 🙂 That's why it's not used here, withgrunt
pulling all the task running weight.
qunit-fixture
that can be used for instantiating a DOM. So essentially this means that you can place any UI5 view/controller-pair there in order to interact with and test.<!-- ... -->
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
</body>
<!-- ... -->
Input
control that sets a special placeholder:**
* @namespace
* @name sap.ui.demo.todo
*/
sap.ui.define([
"sap/m/Input"
],
/**
* @param {sap.m.Input} Input class of UI5
*
* @returns {sap.ui.demo.todo.control.Input} custom Input field
*/
function (Input) {
"use strict";
/**
* custom Input field uses calculation rules and/or values for displaying content.
*
* @name sap.ui.demo.todo.control.Input
* @extends sap.m.Input
*/
return Input.extend("sap.ui.demo.todo.control.Input", /** @lends sap.ui.demo.todo.control.Input.prototype */ {
/**
* re-use renderer from sap.m.Input
*/
renderer: {},
/**
* initialize control
*/
init: function () {
Input.prototype.init.call(this);
this.setPlaceholder("whaaat");
}
});
}
);
qunit-fixture
and instantiating/rendering it there, testing the control becomes a lot more managable.<mvc:View xmlns:mvc="sap.ui.core.mvc"
xmlns:todo="sap.ui.demo.todo.control"
controllerName="sap.ui.demo.todo.test.unit.view.App"
displayBlock="true">
<todo:Input id="theCustomControlInput"/>
</mvc:View>
Note: there is an accompanying conroller in webapp/test/unit/view/App.controller.js
, but it's skeleton-only, no special methods defined.
QUnit.test("test a custom control (working in a 'real' XML view) in a Unit Test", function (assert) {
var fnDone = assert.async();
XMLView.create({
viewName: "sap/ui/demo/todo/test/unit/view/AppWithInputCC"
})
.then(function (oView) {
return oView.placeAt("qunit-fixture");
})
.then(function (oView) {
var oController = oView.getController();
var oInput = oController.byId("theCustomControlInput");
return assert.strictEqual(oInput.getPlaceholder(), "whaaat", "Placeholder checked out fine");
})
.then(fnDone)
.catch(function (oError) {
// do sth clever here
fnDone();
})
})
qunit-fixture
gets torn down after every test, so no interference potential with other DOM elements or UI5 controls in subsequent tests.<List id="theList">
<dragDropConfig>
<dnd:DragDropInfo
sourceAggregation="items"
targetAggregation="items"
dropPosition="OnOrBetween"
drop="onDrop"
/>
</dragDropConfig>
<items>
<StandardListItem title="item 0"/>
<StandardListItem title="item 1"/>
<StandardListItem title="item 2"/>
<StandardListItem title="item 3"/>
<StandardListItem title="item 4"/>
</items>
</List>
{d|D}ragDrop*
declarations enable native browser-level events for dragging and dropping list items. What to do after drop
ping needs to be handled on controller level (drop=...
).QUnit.test("testing Drag and Drop", function (assert) {
XMLView.create()
.then(function (oView) {
oView.placeAt("qunit-fixture");
return oView;
})
.then(function (oView) {
var oController = oView.getController();
var oList = oController.byId("theList");
// we need to stay in the rendering cycle for drag and drop,
// requiring a valid DOM
// -> view needs to be rendered first before we can drag and drop anything
oView.attachAfterRendering(function () {
// test-logic goes here!
});
})
.then(...)
.catch(...)
});
getDomRef()
and worked with:function triggerEvent(oControl, sType, mOffset) {
// ...
oControl.getDomRef().dispatchEvent(oDndEvent);
}
Event
:function emulateDragDropEvent(sEventType) {
var oEvent = new Event(sEventType, {
bubbles: true,
cancelable: true
});
oEvent.dataTransfer = new DataTransfer(); // <- muy importante!
return oEvent;
}
dataTransfer
property of the event - it again uses the browser-native DataTransfer
to equip the Drag'n'Drop-event with information on what DOM nodes are being moved around.dataTransfer
property will loose you any programmatic interaction capabilities!// start dragging the first list item
triggerEvent(oList.getItems()[0], "dragstart");
triggerEvent(oSourceListItem, "dragstart");
triggerEvent(oTargetListItem, "dragenter");
triggerEvent(oTargetListItem, "dragover");
triggerEvent(oTargetListItem, "drop");
triggerEvent(oTargetListItem, "dragend");
DnD
.grab(oListItem1)
.dragOn(oListItem4)
.drop()
.grab(oNextToLastListItem)
.dragAfter(oLastListItem)
.drop();
assert.strictEqual(oList.getItems()[0].getTitle(), "item 1", "1 is now the first item");
assert.strictEqual(oList.getItems()[2].getTitle(), "item 0", "0 is at position 3");
assert.strictEqual(oList.getItems()[4].getTitle(), "item 3", "3 moved after 4!")
browser.actions().dragAndDrop(node1, node2)
.jQuery
is just so...easy:// webapp/test/integration/pages/App.js
return this.waitFor({
controlType: "sap.m.App",
matchers: function (oApp) {
var vMap = jQuery("#" + oApp.getId()).find("*");
_.each(vMap, function ($oControl) {
var oControl = jQuery($oControl).control();
// ...
});
},
find()
on the App ID delivers all its' descendant DOM nodes.control()
extension to jQuery (https://ui5.sap.com/#/api/jQuery/methods/control) converts the DOM node back to its' UI5 control representation.// webapp/test/integration/NegativeJourney.js
opaTest("make sure control doesn't exist", function (Given, When, Then) {
Then.onTheAppPage
.iMakeSureThereIsNo("module", "sap.m.Bla")
.and.iMakeSureThereIsNo("id", "whatEver");
});
// webapp/test/integration/pages/App.js
iMakeSureThereIsNo: function (sKind, sIdentifier) {
//...
var oControl = jQuery($oControl).control();
if (oControl && oControl[0]) {
switch (sKind) {
case "module":
if (oControl[0].getMetadata().getName() === sIdentifier) {
aMatches.push(oControl[0])
}
break;
case "id":
if (oControl[0].getId() === sIdentifier) {
aMatches.push(oControl[0]);
}
}
}
// ...
return aMatches.length === 0;
}
iMakeSureThereIsNo(sKind, sIdentifier)
in order to get that complex matter sorted.// webapp/test/e2e/TodoAppVisual.spec.js
describe('TodoAppVisual', function() {
it('should compare the start page screenshot to the reference image', function () {
expect(takeScreenshot()).toLookAs('appStarted');
});
});
visual
intended for visual tests is...well..suboptimal. A better approach is to reuse the integration
profile and enhance it with screenshot capabilities:// webapp/test/e2e/conf.js
// ...
profile: 'integration',
baseUrl: 'http://localhost:8080/index.html',
take: true,
compare: true,
update: false,
storageProvider: {
name: './image/localStorageProvider',
refImagesRoot: './target',
actImagesRoot: './target'
},
screenshotProvider: {
name: './image/localScreenshotProvider',
screenshotSleep: 100
},
comparisonProvider: {
name: './image/localComparisonProvider'
}
// these are important for reference image storage!
// browsers:[{
// browserName: (_chrome_|chromeMobileEmulation|chromeHeadless|firefox|ie|safari|edge),
// browserVersion: '*',
// platformName: (_windows_|mac|linux|android|ios|winphone),
// platformVersion: '*',
// platformResolution: 'WIDTHxHEIGHT',
// ui5.theme: (bluecrystal|_belize_|hcp),
// ui5.direction: (rtl|_ltr_),
// ui5.mode: (_cozy_|compact)
//}]
// ...
take
instructs UIveri5 to take screenshots, compare
to compare the taken screenshot against a reference image (more on that below), and update
to -well- update the reference screenshot.{storage|screenshot|comparison}Provider
) make UIveri5 use local (aka on your computer) tooling rather than remote capabilities. BTW: major tool in use for image comparison is Resemble.js!Heads up: the following is not the standard way of using UIveri5, so proceed with caution. There's some hacky parts, it's not an out-of-the-box visual comparison supersuite!
grunt serve
.update: true
in the above config file. The reference images are stored in a folder hierarchy pertaining to the browsers
config options above:/<app dir>/target/images/<testCase>⏎
/<platform>/<resolution>/browser⏎
/<theme>/<direction>/<mode>⏎
/<imgName from testCase>.ref.png
example: /webapp/test/e2e/target/images/TodoAppVisual⏎
/mac/1280x1024/firefox⏎
/belize/ltr/cozy⏎
/appStarted.ref.png
uiveri5
from the command line and let it do the work - note that the tests in your *.spec.js
will all fail during this very first run due to the lack of reference screen shots.update: false
in the config file, and run uiveri5
again (don't forget grunt serve
!) - this is what happens:takeScreenshot()
is used, the current state of the application is screenshotted (sp?) and stored in/<app dir>/target/report/screenshots/<testCase>-<test name>_<img index>_<pass|fail>_<timestamp>.png
webapp/test/e2e/target/report/screenshots/TodoAppVisual-should-compare-the-start-page-screenshot-to-the-reference-image_0_pass_2019-08-06T10-43-24.png
expect(...).toLookAs('appStarted')
webapp/test/e2e/target/report/screenshots/report.html
uiveri5 --browsers=firefox,safari,chrome
the possibilites start showing.You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
10 | |
9 | |
7 | |
6 | |
4 | |
4 | |
3 | |
3 | |
3 | |
3 |