Compose Methods
To combine several element actions, such as a method for login that sets a username, sets a password, and clicks a button, declare a compose
method.
Method Properties
A compose
method has these properties:
name
(Required) String. The unique name of the method within this JSON file.description
(Optional) String or Object. See Method Description.compose
(Required) Array. Each object is an element and a method.element
(Optional) String. The name of a basic element from the same JSON file. Defaults toself
, which is a reference to the current page object. When a statement refers to the current page object, it can apply the page object's methods. Specify an element only if you need to operate on or interact with it. For an explicit wait using thewaitFor
keyword, omit the element.apply
(Optional) String. If this property isn't defined, the getter method for theelement
property is called.- Basic element: the name of a supported action to apply to the element.
- Custom element: any public method.
args
(Optional) Array. If theapply
action needs parameters, provide them in an array. Each element of the array has these properties:name
(Required) String. A parameter name that is unique in the scope of theargs
array.type
(Required) String. One of these primitive types:string
,number
, orboolean
.
matcher
(Optional) Object. Defines the filter criteria for the data returned by theapply
method or the element's getter method.type
(Required) String. The matcher type for filtering data. For supported types, see Matchers.args
(Optional) Array. If the matcher type requires arguments, pass them in this array.
returnAll
(Optional) Boolean. Iftrue
, the method returns an array (in JavaScript) or a list (in Java) of objects. The default isfalse
.returnType
(Optional) String. Explicitly set the return type for the method. You can set a primitive type, such as"string"
,"number"
,"boolean"
, or a custom type, such as"my/page/object"
. This property is optional because the return type for a compose method can sometimes be inferred by the UTAM compiler. For more information, see Method Return Types.
args
(Optional) Array. To access an argument passed to a compose method in multiple statements, declare a reusable argument at the method level. For more information, see Argument Reference.
Method Descriptions
A compose method or element can have a description that explains its usage, return value, and parameters. The description is used to generate JavaDoc or JSDoc.
The simplified description
format is one string property:
{
"methods": [
{
"name": "myMethod",
"description": "Gets an attribute of the root element",
"compose": [
{
"element": "root",
"apply": "getAttribute",
"args": [
{
"name": "attrName",
"type": "string"
}
]
}
]
}
]
}
The generated method includes the following Javadoc or JSDoc. By default, the @return
tag contains the inferred return type and the @param
tag has the name and type of the parameter:
/**
* Gets an attribute of the root element
*
* @return String
* @param attrName String
*/
The extended object description
format enables you to describe more information, such as the return value:
{
"methods": [
{
"name": "myMethod",
"description": {
"text": ["Gets an attribute of the root element"],
"return": "string with an attribute value",
"throws": "NullPointerException if the attribute name is null",
"deprecated": "in Summer '22 release"
},
"compose": [
{
"element": "root",
"apply": "getAttribute",
"args": [
{
"name": "attrName",
"type": "string",
"description": "string with attribute name"
}
]
}
]
}
]
}
text
string array describing what the method or element doesreturn
(Optional) string that describes the return valuethrows
(Optional) string that describes a thrown exception and when it's throwndeprecated
(Optional) if the method is no longer supported, this string explains when and why the method was deprecated.
To provide a description for the parameter, we added a
description
property for theattrName
argument. The description is only possible for non-literal (not hardcoded by value) arguments.
The generated method has the following Javadoc or JSDoc:
/**
* Gets an attribute of the root element
*
* @return string with attribute value
* @param attrName string with attribute name to get
* @throws NullPointerException if the attribute name is null
* @deprecated in Summer '22 release
*/
In Java, the generated method is marked with an @Deprecated
annotation.
The same description
format can be added to any element:
{
"elements": [
{
"public": true,
"name": "custom",
"type": "utam/pageObjects/MyCustomObject",
"description": "get area inside table",
"selector": {
"css": "css%s",
"args": [
{
"name": "selectorArg",
"type": "string",
"description": "parameter description"
}
]
}
}
]
}
Return Types
See Method Return Types.
Invoke action for a basic element
This compose method sets text on the root element and clicks a submit button.
{
"type": ["editable"],
"elements": [
{
"name": "submitBtn",
"type": ["clickable"],
"selector": {
"css": ".submit"
}
}
],
"methods": [
{
"name": "submitForm",
"compose": [
{
"element": "root",
"apply": "setText",
"args": [
{
"type": "string",
"name": "stringToEnter"
}
]
},
{
"element": "submitBtn",
"apply": "click"
}
]
}
]
}
Here's the generated Java code:
public void submitForm(String stringToEnter) {
getRoot().setText(stringToEnter);
submitBtn.click();
}
Here's the generated JavaScript code:
async submitForm(stringToEnter) {
const _statement0 = await this.__getRoot();
await _statement0.setText(stringToEnter);
const _statement1 = await this.__getSubmitBtn();
const _result1 = await _statement1.click();
return _result1;
}
Invoke method from the same page object
The invokeSubmitForm
method simply invokes the submitForm
method declared in the same page object.
{
"elements": [
// ...
],
"methods": [
{
"name": "submitForm",
"compose": [
// ...
]
},
{
"name": "invokeSubmitForm",
"compose": [
{
"apply": "submitForm",
"args": [
{
"type": "string",
"name": "stringToEnter"
}
]
}
]
}
]
}
This pattern is useful if you want to reuse the same method in multiple compose statements. For example, you could call submitForm
from login
and loginWithDeepLink
methods.
Here's the generated JavaScript code:
async invokeSubmitForm(stringToEnter) {
const _result0 = await this.submitForm(stringToEnter);
return _result0;
}
Invoke an element's getter
This compose method invokes the getter for the myCustomComponent
element.
{
"elements": [
{
"name": "myCustomComponent",
"type": "my/custom/component",
"selector": {
"css": "custom-component"
}
}
],
"methods": [
{
"name": "composeGettingCustomElement",
"compose": [
{
"element": "myCustomComponent"
}
]
}
]
}
Here's the generated JavaScript code.
async composeGettingCustomElement() {
const _result0 = await this.__getMyCustomComponent();
return _result0;
}
Invoke method from a different page object
This compose method applies the someUnknownPublicMethod
method to the myCustomComponent
custom element.
{
"elements": [
{
"name": "myCustomComponent",
"type": "my/custom/component",
"selector": {
"css": "custom-component"
}
}
],
"methods": [
{
"name": "invokeCustomElementMethod",
"compose": [
{
"element": "myCustomComponent",
"apply": "someUnknownPublicMethod"
}
]
}
]
}
Note that the compiler can't know if the someUnknownPublicMethod
method actually exists, and what are its return value or parameters. The responsibility to validate those things belongs to the developer or a preruntime compilation step (depending on the setup).
Here's the generated JavaScript code:
async invokeCustomElementMethod() {
const _statement0 = await this.__getMyCustomComponent();
const _result0 = await _statement0.someUnknownPublicMethod();
return _result0;
}
Matchers
Use a matcher to transform the return value of a compose statement.
This compose statement uses a matcher to return a non-null value.
{
"name": "matcherNotNull",
"compose": [
{
"element": "single",
"apply": "getAttribute",
"args": [
{
"value": "\"readonly\""
}
],
"matcher": {
"type": "notNull"
}
}
]
}
For more information on matchers, see Element Filters: Matchers.
Chain Compose Statements
It's possible to "chain" compose statements. A chain applies a method or a getter to the result of the previous statement.
Important: Chains are supported only if the previous statement returns a custom type (another page object).
To apply a method or a getter from the current statement to the result of the previous statement, use the "chain": true
property inside a statement.
The chain methods approach has some disadvantages and limitations:
- Because the referenced elements and methods (except the first) are defined in other JSON files, the UTAM compiler can’t validate the correctness of the chain until the page object is generated. If the element doesn’t exist, if it isn’t public, or if the type is incorrect, the problem is discovered when the generated code is compiled.
- The current page object now depends on the content of other page objects, so changes in those might prevent the page object from compiling. We notice the issue during build, but it's more difficult to manage.
- The page object author might not be aware of the internals. If another team adds a chained method, ownership of the page object is unclear.
Here are some examples of chaining compose statements.
Compose container element with getter and public action
Consider the following test code samples:
const myModal = await utam.load(MyModalWithDynamicContent);
// long version
const footerArea = await myModal.getContent(FooterAreaWrapper);
const footerButtonsPanel = await footerArea.getFooterButtonsPanel();
await footerButtonsPanel.clickButtonByIndex(1);
// short version
await myModal.clickSave();
It's possible to compose the long version into a short version using a chain.
{
"elements": [
{
"name": "content",
"type": "container"
}
],
"methods": [
{
"name": "clickSave",
"compose": [
{
// invoke container method with hardcoded type
"element": "content",
"args": [
{
"type": "pageObject",
"value": "my/pageObjects/FooterAreaWrapper"
}
],
"returnType": "my/pageObjects/FooterAreaWrapper"
},
{
// invoke getter
"chain": true,
"element": "footerButtonsPanel",
"returnType": "my/pageObjects/FooterButtonsPanel"
},
{
// invoke public method
"chain": true,
"apply": "clickButtonByIndex",
"args": [
{
"value": 1
}
]
}
]
}
]
}
Chaining list to a list
If both previous and current statements return lists ("returnAll": true
), the method is applied to each returned element using flatMap
.
Consider the use case of a table with multiple rows and cells. Assume that each row and cell is a separate component. Let's write a method that returns all cells inside a table.
- table row JSON
{
"elements": [
{
"type": "my/pageObjects/tableCell",
"name": "tableCells",
"selector": {
"css": "table-cell",
"returnAll": true
}
}
]
- table JSON
{
"elements": [
{
"type": "my/pageObjects/tableRow",
"name": "tableRows",
"selector": {
"css": "table-row",
"returnAll": true
}
}
],
"methods": [
{
"name": "getAllCells",
"compose": [
{
"returnType": "my/pageObjects/tableRow",
"returnAll": true,
"element": "tableRows"
},
{
"chain": true,
"returnType": "my/pageObjects/tableCell",
"returnAll": true,
"element": "tableCells"
}
]
}
]
}
Generated Java code:
public final List<TableCell> getAllCells() {
List<TableRow> statement0 = this.getTableRowsElement();
List<TableCell> statement1 =
statement0
.stream()
.flatMap(element -> element.getTableCells().stream())
.collect(Collectors.toList());
return statement1;
}
Compose Container Invocation
A pageObject
type parameter can be used to invoke a container method inside a compose statement.
{
"elements": [
{
"name": "containerElement",
"type": "container"
}
],
"methods": [
{
"name": "composeContainerHardcoded",
"compose": [
{
"element": "containerElement",
"args": [
{
"type": "pageObject",
"value": "utam-tests/pageObjects/myPageObject"
}
],
"returnType": "utam-tests/pageObjects/myPageObject"
}
]
},
{
"name": "composeContainer",
"compose": [
{
"element": "containerElement",
"args": [
{
"type": "pageObject",
"name": "pageObjectCtor"
}
]
}
]
}
]
}
Generated JavaScript code:
// declaration
composeContainerHardcoded(): Promise<unknown>;
composeContainer<T extends _UtamBasePageObject>(pageObjectCtor: _PageObjectCtor<T>): Promise<unknown>;
// implementation
import _MyPageObject from 'utam-tests/pageObjects/myPageObject';
async composeContainerHardcoded() {
const _result0 = await this.__getContainer(_MyPageObject);
return _result0;
}
async composeContainer(pageObjectCtor) {
const _result0 = await this.__getContainer(pageObjectCtor);
return _result0;
}
Compose Entering Frame
Frame and Root Page Object type parameters can be used to compose entering a frame:
{
"elements": [
{
"name": "frameElement",
"type": "frame",
"selector": {
"css": "#frame"
}
}
],
"methods": [
{
"name": "composeEnterFrameHardcoded",
"compose": [
{
"element": "document",
"returnType": "utam-tests/pageObjects/frameArea",
"apply": "enterFrameAndLoad",
"args": [
{
"type": "elementReference",
"value": "frameElement"
},
{
"type": "rootPageObject",
"value": "utam-tests/pageObjects/frameArea"
}
]
}
]
},
{
"name": "composeEnterFrame",
"compose": [
{
"element": "document",
"returnType": "rootPageObject",
"apply": "enterFrameAndLoad",
"args": [
{
"type": "frame",
"name": "frameElementParameter"
},
{
"type": "rootPageObject",
"name": "pageObjectCtor"
}
]
}
]
}
]
}
Generated JavaScript code:
// declaration
import _FrameArea from 'utam-tests/pageObjects/frameArea';
composeEnterFrameHardcoded(): Promise<_FrameArea>;
composeEnterFrame<T extends _UtamBaseRootPageObject>(frameElementParameter: _FrameUtamElement, pageObjectCtor: _PageObjectCtor<T>): Promise<T>;
// implementation
import _FrameArea from 'utam-tests/pageObjects/frameArea';
async composeEnterFrameHardcoded() {
const _statement0 = await this.getDocument();
const _result0 = await _statement0.enterFrameAndLoad(await this.__getFrameElement(), _FrameArea);
return _result0;
}
async composeEnterFrame(frameElementParameter, pageObjectCtor) {
const _statement0 = await this.getDocument();
const _result0 = await _statement0.enterFrameAndLoad(frameElementParameter, pageObjectCtor);
return _result0;
}