loio |
---|
21aeff6928f84d179a47470123afee59 |
view on: demo kit nightly build | demo kit latest release
Common use cases for retrieving controls
new sap.ui.test.Opa5().waitFor({
id : "page-title",
viewName : "Category",
viewNamespace : "my.Application.",
success : function (oTitle) {
Opa5.assert.ok(oTitle.getVisible(), "the title was visible");
}
});
In this example, we search for a control with the ID page-title
. The control is located in the my.Application.Category
view.
By default, OPA5 tries to find the element until the default timeout of 15 seconds is reached. You can override this behavior by passing it as a parameter to the waitFor
function. Zero means infinite timeout.
new sap.ui.test.Opa5().waitFor({
id : "productList",
viewName : "Category",
success : function (oList) {
Opa5.assert.ok(oList.getItems().length, "The list did contain products");
},
timeout: 10
});
In this example, the check
function is omitted. In this case, OPA5 creates its own check
function that waits until the control is found or the specified timeout is reached.
Sometimes you need to test for a control that has no explicit ID set and maybe you can't or don't want to provide one for your test. To get around this issue, use a custom check function to filter for this control. Let's assume we have a view called Detail
and there are multiple sap.m.ObjectHeaders
on this page. We want to wait until there's an object header with the title myTitle
.
To do this, use the following code:
return new Opa5().waitFor({
controlType : "sap.m.ObjectHeader",
viewName : "Detail",
matchers : new sap.ui.test.matchers.PropertyStrictEquals({
name : "title",
value: "myTitle"
}),
success : function (aObjectHeaders) {
Opa5.assert.strictEqual(aObjectHeaders.length, 1, "was there was only one Object header with this title on the page");
Opa5.assert.strictEqual(aObjectHeaders[0].getTitle(), "myTitle", "was on the correct Title");
}
});
Since no ID is specified, OPA passes an array of controls to the check
function. The array contains all visible object header instances in the Detail
view. However, a built-in support for comparing properties doesn't exist, so we implement a custom check.
For more information about all matchers, see the API Reference and the Samples.
sap.ui.test.matchers.Properties
: This matcher checks if the controls have properties with given values. The values can also be defined as regular expressions (RegExp
) for the string type properties.
return new Opa5().waitFor({
controlType : "sap.m.StandardListItem",
matchers : new sap.ui.test.matchers.Properties({
text: new RegExp("laptop", "i"),
selected: true
}),
success : function (aItems) {
Opa5.assert.ok(aItems[0], "Item is selected")
},
errorMessage: "No selected item found"
});
sap.ui.test.matchers.Properties
andsap.ui.test.matchers.PropertyStrictEquals
serve the same purpose but it's easier to pass parameters tosap.ui.test.matchers.Properties
.
sap.ui.test.matchers.Ancestor
: This matcher checks if the control has the specified ancestor (ancestor is of a control type).
var oOpa = new Opa5();
return oOpa.waitFor({
controlType: "sap.m.List",
success: function (oList) {
return oOpa.waitFor({
controlType : "sap.m.StandardListItem",
matchers : new sap.ui.test.matchers.Ancestor(oList),
success : function (aItems) {
Opa5.assert.ok(aItems.length, "Found list items")
},
errorMessage: "No list items found"
});
}
});
sap.ui.test.matchers.Descendant
: This matcher checks if the control has the specified descendant. In this example, we search for a table row, which has a text control with a certain value.
this.waitFor({
controlType: "sap.m.Text",
matchers: new Properties({
text: "special text"
}),
success: function (aText) {
return this.waitFor({
controlType: "sap.m.ColumnListItem",
matchers: new Descendant(aText[0]),
success: function (aRows) {
Opa5.assert.strictEqual(aRows.length, 1, "Found row with special text");
},
errorMessage: "Did not find row or special text is not inside table row"
});
},
errorMessage: "Did not find special text"
});
sap.ui.test.matchers.BindingPath
: This matcher checks if the controls have specified data binding paths. The path
property matches controls by their binding context. Controls with a binding context are usually inside an aggregation or have a parent control with data binding. The propertyPath
property matches controls by the data binding paths of their own properties. Binding property paths can be part of an expression binding. You can set the path
and propertyPath
properties separately or in combination.For a practical example of the various types of data binding, see the Tutorial Samples.
// Match a CheckBox located inside a ListItem:
// the CheckBox has a property binding with relative path "Selected"
// the ListItem has a binding context path "/products/0"
return new Opa5().waitFor({
controlType : "sap.m.CheckBox",
matchers : new sap.ui.test.matchers.BindingPath({
path: "/products/0",
propertyPath: "Selected"
}),
success : function (aResult) {
Opa5.assert.ok(aResult[0], "CheckBox is matched")
}
});
sap.ui.test.matchers.I18NText
: This matcher checks if a control property has the same value as a text from an I18N resource bundle.
return new Opa5().waitFor({
controlType : "sap.m.Button",
matchers : new sap.ui.test.matchers.I18NText({
propertyName: "text",
key: "search"
}),
success : function () {
Opa5.assert.ok(true, "Search button is matched");
}
});
As of version 1.95,
sap.ui.test.matchers.I18NText
is extended to allow using library resource bundle.
return new Opa5().waitFor({
controlType : "sap.m.Button",
matchers : new sap.ui.test.matchers.I18NText({
propertyName: "text",
key: "VIEWSETTINGS_ACCEPT",
useLibraryBundle: true
}),
success : function () {
Opa5.assert.ok(true, "OK button is matched");
}
});
You can also define a matcher as an inline function: The first parameter of the function is a control to match. If the control matches, return true
to pass the control on to the next matcher and/or to check and success functions.
return new Opa5().waitFor({
controlType : "sap.m.StandardListItem",
matchers : function(oItem) {
return oItem.$().hasClass("specialItem");
},
success : function (aItems) {
Opa5.assert.ok(aItems.length, "Found special items")
},
errorMessage: "No special items found"
});
If you return a 'truthy' value from the matcher, but not a Boolean, it will be used as an input parameter for the next matchers and/or check and success. This allows you to build a matchers pipeline.
return new Opa5().waitFor({
controlType : "sap.m.StandardListItem",
matchers : [
function(oItem) {
// returns jQuery instance of control
return oItem.$().length && oItem.$();
},
function($item) {
// $item is the matching control's jQuery instance
return $item.hasClass("specialItem");
}
],
actions : function ($item) {
// $item is the matching control's jQuery instance
$item.trigger("click");
},
errorMessage: "No special items found"
});
sap.ui.test.matchers.LabelFor
: This matcher checks if a given control is associated with an sap.m.Label
control. This means that there should be a label on the page with a labelFor
association to the control. The label can be filtered by text value or by the i18n
key of a given property value. Note that some controls, such as sap.ui.comp.navpopover.SmartLink
, sap.m.Link
, sap.m.Label
, and sap.m.Text
can't be matched by sap.ui.test.matchers.LabelFor
as they can't have an associated label.
Using the i18n
key:
return new Opa5().waitFor({
controlType: "sap.m.Input",
// Get sap.m.Input which is associated with Label which have i18n text with key "CART_ORDER_NAME_LABEL"
matchers: new sap.ui.test.matchers.LabelFor({ key: "CART_ORDER_NAME_LABEL", modelName: "i18n" }),
// It will enter the given text in the matched sap.m.Input
actions: new sap.ui.test.actions.EnterText({ text: "MyName" })
});
Using the text
property:
return new Opa5().waitFor({
controlType: "sap.m.Input",
// Get sap.m.Input which is associated with Label which have i18n text with text "Name"
matchers: new sap.ui.test.matchers.LabelFor({ text: "Name" }),
// It will enter the given text in the matched sap.m.Input
actions: new sap.ui.test.actions.EnterText({ text: "MyName" }),
success: function (oInput) {
Opa5.assert.ok(oInput.getValue() === "MyName", "Input value is correct");
}
});
For more information, see the API Reference and the Sample.
Use the option searchOpenDialogs
to restrict control search to open dialogs only. You can combine searchOpenDialogs
with controlType
or any predefined or custom matcher. As of version 1.62, the ID option is also effective in combination with searchOpenDialogs
. If the dialog is inside a view, the viewName
option ensures that the given ID is relative to the view. Otherwise, the search is done by global ID.
This is an example of matching a control with ID mainView--testButton
located inside a dialog. The dialog itself is part of a view with name main.view
and ID mainView
:
this.waitFor({
searchOpenDialogs: true,
id: "testButton",
viewName: "main.view"
actions: new sap.ui.test.actions.Press(),
errorMessage : "Did not find the dialog button"
});
The next example shows the use case where we want to press a button with 'Order Now' text on it inside a dialog.
To do this, we set the searchOpenDialogs
option to true and then restrict the controlType
we want to search for to sap.m.Button
. We use the check function to search for a button with the text 'Order Now' and save it to the outer scope. After we find it, we trigger a tap
event:
iPressOrderNow : function () {
var oOrderNowButton = null;
this.waitFor({
searchOpenDialogs : true,
controlType : "sap.m.Button",
check : function (aButtons) {
return aButtons.filter(function (oButton) {
if(oButton.getText() !== "Order Now") {
return false;
}
oOrderNowButton = oButton;
return true;
});
},
actions: new sap.ui.test.actions.Press(),
errorMessage : "Did not find the Order Now button"
});
return this;
}
As of version 1.63, you can limit control search to a fragment with the option fragmentId
.
fragmentId
is effective only when searching by control ID inside a view. Whether a control belongs to a fragment is only relevant when the fragment has a user-assigned ID, which is different from the ID of its parent view. In this case, the fragment ID takes part in the formation of control ID and you have to use the fragmentId
option to simplify test maintenance.
The next example shows the use case where we want to press a button with ID theMainView--greeting--helloWorld
, located inside a fragment with ID greeting
and view with ID theMainView
:
this.waitFor({
viewId: "theMainView",
fragmentId: "greeting",
id: "hello",
actions: new Press(),
errorMessage : "Did not find the Hello button"
});
In OPA5, you can look for controls that are invisible, disabled, or noninteractable by using the respective waitFor
boolean properties: visible
, enabled
, and interactable
.
You need a more creative approach to verify that no controls on the page match a certain criteria. One idea is to verify that a parent doesn't have a given child. Locate the parent using OPA5 standard matchers and then use a custom check
function to iterate over the children of the parent. The result of check
should be truthy if no children match a given condition.
The following example shows a custom check
function that returns true
if a popover doesn't contain a button with a certain text.
this.waitFor({
controlType: "sap.m.Popover",
success: function (aPopovers) {
return this.waitFor({
check: function () {
var aPopoverContent = aPopovers[0].getContent();
var aButtons = aPopoverContent.forEach(function (oChild) {
return oChild.getMetadata().getName() === "sap.m.Button" &&
oChild.getText() === "Another text";
});
return !aButtons || !aButtons.length;
},
success: function () {
Opa5.assert.ok(true, "The popover button is missing");
},
errorMessage: "The popover button is present"
});
}
});
As of version 1.65, you can search for controls by their enabled state using the enabled
property. When enabled
is set to true
, only enabled controls will match. When enabled
is set to false
, both enabled and disabled controls will match.
By default, only enabled controls are matched when:
-
autoWait
is set totrue
, or -
there are actions defined in the
waitFor
If autoWait
is disabled and there are no actions, the search matches disabled controls as well.
The next example shows that the enabled
property has priority over autoWait
:
this.waitFor({
controlType: "sap.m.Button",
enabled: false,
autoWait: true,
success: function () {...}
})
UI elements can be recursive, for example in a tree. Instead of triggering the action for each known element, you can also define it recursively (see the code snippet below). OPA ensures that the waitFor
statements triggered in a success handler are executed before the next arrangement, action, or assertion. That also allows you to work with an unknown number of entries, for example in a list. First, you wait for the list, and then trigger actions on each list item.
iExpandRecursively : function() {
return this.waitFor({
controlType : "sap.m.StandardTreeItem",
matchers : new sap.ui.test.matchers.PropertyStrictEquals({
name : "expanded",
value : false
}),
actions : function (oTreeNode) {
oTreeNode.getTree().expandToLevel(oTreeNode.getLevel() + 1)
that.iExpandRecursively();
},
errorMessage : "Didn't find collapsed tree nodes"
});
}
As of version 1.72, OPA5 supports the declarative matcher syntax that allows you to declare built-in matchers in a literal object. A matcher declaration is a JSON object. The OPA5 waitFor
statement is simplified by using a single JSON object, instead of the more verbose matcher instances. Only built-in matchers are allowed. Inline matcher functions and custom matcher instances are only allowed in the matchers waitFor
parameter:
return this.waitFor({
controlType : "sap.m.Text",
matchers : function () {
// ...
}
});
There are two places you can add a matcher declaration in a waitFor
object:
-
On the top level
In this case, if you use an unknown matcher, an exception is thrown, stating that the parameter isn't defined in OPA5 API.
this.waitFor({ controlType : "sap.m.Text", propertyStrictEquals: { name : "text", value : "foo" } });
-
In the
matchers
parameterIn this case, if you use an unknown matcher, an exception is thrown, stating that the matcher isn't supported.
this.waitFor({ controlType : "sap.m.Text", matchers: { propertyStrictEquals: { name : "text", value : "foo" } } });
If there are matchers declared in both places, they're combined.
A matcher is declared by its name and properties. The name is a key in the matchers object literal and has to start with a lower-case letter. For example, to declare an sap.ui.test.matchers.Properties
matcher, use the name properties
. Every matcher accepts a specific set of properties, which has to be declared as a value in the matchers object. This value has to be an object literal. Behind the scenes, every matcher declaration is transformed into a matcher instance. Every value in the declaration represents the properties that are fed to one matcher instance. There's an example for every built-in matcher in the API documentation.
The following two waitFor
statements produce the same set of matchers:
// declaration
this.waitFor({
controlType : "sap.m.Text",
matchers: {
propertyStrictEquals: {
name : "text",
value : "foo"
}
}
});
// instantiation
this.waitFor({
controlType : "sap.m.Text",
matchers: new PropertyStrictEquals({
name : "text",
value : "foo"
})
});
If you have to use one matcher twice, the value for the matcher must be an array. Each element of the array has to be an object literal that is used by one matcher instance. This is useful when you have to match a control by two or more of its properties.
The following two waitFor
statements produce the same set of matchers:
// declaration
this.waitFor({
matchers: {
properties: [{
text: "hello"
}, {
number: 0
}]
}
});
// instantiation
this.waitFor({
matchers: [
new PropertyStrictEquals({
name : "text",
value : "foo"
}),
new PropertyStrictEquals({
name : "number",
value : 0
})
]
});
When declaring an sap.ui.test.matchers.Ancestor
or sap.ui.test.matchers.Descendant
, you have to declare which control is ancestor or descendant. With matcher instances, you simply pass the control instance that you have already located in a previous waitFor
statement. Keep in mind that with matcher declarations you can't use object instances or functions as values. The solution is to use a nested declarative matcher for the ancestor or descendant. It's assumed that it will match exactly one control and if it doesn't, any one of the matches is used. This is a special matcher, which supports a superset of matchers, such as, controlType
, ID
, and any other JSON-compatible properties available in a typical waitFor
statement.
The following two waitFor
statements produce the same result:
// declaration
this.waitFor({
controlType: "sap.m.Text",
matchers: {
ancestor: {
controlType : "sap.m.Bar",
properties: {
text: "hello"
}
}
}
});
// instantiation
this.waitFor({
controlType : "sap.m.Bar",
matchers: new Properties({
text: "hello"
}),
success: function (aAncestors) {
var oAncestor = aAncestors[0]; // order not guaranteed
return this.waitFor({
controlType: "sap.m.Text",
matchers: new Ancestor(oAncestor)
});
}
});