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 to self, 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 the waitFor keyword, omit the element.
    • apply (Optional) String. If this property isn’t defined, the getter method for the element 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 the apply 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 the args array.
      • type (Required) String. One of these primitive types: string, number, or boolean.
    • matcher (Optional) Object. Defines the filter criteria for the data returned by the apply 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. If true, the method returns an array (in JavaScript) or a list (in Java) of objects. The default is false.
    • 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:

1{
2  "methods": [
3    {
4      "name": "myMethod",
5      "description": "Gets an attribute of the root element",
6      "compose": [
7        {
8          "element": "root",
9          "apply": "getAttribute",
10          "args": [
11            {
12              "name": "attrName",
13              "type": "string"
14            }
15          ]
16        }
17      ]
18    }
19  ]
20}

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:

1/**
2 * Gets an attribute of the root element
3 *
4 * @return String
5 * @param attrName String
6 */

The extended object description format enables you to describe more information, such as the return value:

1{
2  "methods": [
3    {
4      "name": "myMethod",
5      "description": {
6        "text": ["Gets an attribute of the root element"],
7        "return": "string with an attribute value",
8        "throws": "NullPointerException if the attribute name is null",
9        "deprecated": "in Summer '22 release"
10      },
11      "compose": [
12        {
13          "element": "root",
14          "apply": "getAttribute",
15          "args": [
16            {
17              "name": "attrName",
18              "type": "string",
19              "description": "string with attribute name"
20            }
21          ]
22        }
23      ]
24    }
25  ]
26}
  • text string array describing what the method or element does
  • return (Optional) string that describes the return value
  • throws (Optional) string that describes a thrown exception and when it’s thrown
  • deprecated (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 the attrName argument. The description is only possible for non-literal (not hardcoded by value) arguments.

The generated method has the following Javadoc or JSDoc:

1/**
2 * Gets an attribute of the root element
3 *
4 * @return string with attribute value
5 * @param attrName string with attribute name to get
6 * @throws NullPointerException if the attribute name is null
7 * @deprecated in Summer '22 release
8 */

In Java, the generated method is marked with an @Deprecated annotation.

The same description format can be added to any element:

1{
2  "elements": [
3    {
4      "public": true,
5      "name": "custom",
6      "type": "utam/pageObjects/MyCustomObject",
7      "description": "get area inside table",
8      "selector": {
9        "css": "css%s",
10        "args": [
11          {
12            "name": "selectorArg",
13            "type": "string",
14            "description": "parameter description"
15          }
16        ]
17      }
18    }
19  ]
20}

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.

1{
2  "type": ["editable"],
3  "elements": [
4    {
5      "name": "submitBtn",
6      "type": ["clickable"],
7      "selector": {
8        "css": ".submit"
9      }
10    }
11  ],
12  "methods": [
13    {
14      "name": "submitForm",
15      "compose": [
16        {
17          "element": "root",
18          "apply": "setText",
19          "args": [
20            {
21              "type": "string",
22              "name": "stringToEnter"
23            }
24          ]
25        },
26        {
27          "element": "submitBtn",
28          "apply": "click"
29        }
30      ]
31    }
32  ]
33}

Here’s the generated Java code:

1public void submitForm(String stringToEnter) {
2    getRoot().setText(stringToEnter);
3    submitBtn.click();
4  }

Here’s the generated JavaScript code:

1async submitForm(stringToEnter) {
2    const _statement0 = await this.__getRoot();
3    await _statement0.setText(stringToEnter);
4    const _statement1 = await this.__getSubmitBtn();
5    const _result1 = await _statement1.click();
6    return _result1;
7}

Invoke method from the same page object 

The invokeSubmitForm method simply invokes the submitForm method declared in the same page object.

1{
2  "elements": [
3    // ...
4  ],
5  "methods": [
6    {
7      "name": "submitForm",
8      "compose": [
9        // ...
10      ]
11    },
12    {
13      "name": "invokeSubmitForm",
14      "compose": [
15        {
16          "apply": "submitForm",
17          "args": [
18            {
19              "type": "string",
20              "name": "stringToEnter"
21            }
22          ]
23        }
24      ]
25    }
26  ]
27}

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:

1async invokeSubmitForm(stringToEnter) {
2    const _result0 = await this.submitForm(stringToEnter);
3    return _result0;
4}

Invoke an element's getter 

This compose method invokes the getter for the myCustomComponent element.

1{
2  "elements": [
3    {
4      "name": "myCustomComponent",
5      "type": "my/custom/component",
6      "selector": {
7        "css": "custom-component"
8      }
9    }
10  ],
11  "methods": [
12    {
13      "name": "composeGettingCustomElement",
14      "compose": [
15        {
16          "element": "myCustomComponent"
17        }
18      ]
19    }
20  ]
21}

Here’s the generated JavaScript code.

1async composeGettingCustomElement() {
2    const _result0 = await this.__getMyCustomComponent();
3    return _result0;
4}

Invoke method from a different page object 

This compose method applies the someUnknownPublicMethod method to the myCustomComponent custom element.

1{
2  "elements": [
3    {
4      "name": "myCustomComponent",
5      "type": "my/custom/component",
6      "selector": {
7        "css": "custom-component"
8      }
9    }
10  ],
11  "methods": [
12    {
13      "name": "invokeCustomElementMethod",
14      "compose": [
15        {
16          "element": "myCustomComponent",
17          "apply": "someUnknownPublicMethod"
18        }
19      ]
20    }
21  ]
22}

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:

1async invokeCustomElementMethod() {
2    const _statement0 = await this.__getMyCustomComponent();
3    const _result0 = await _statement0.someUnknownPublicMethod();
4    return _result0;
5}

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.

1{
2  "name": "matcherNotNull",
3  "compose": [
4    {
5      "element": "single",
6      "apply": "getAttribute",
7      "args": [
8        {
9          "value": "\"readonly\""
10        }
11      ],
12      "matcher": {
13        "type": "notNull"
14      }
15    }
16  ]
17}

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.

Chains are supported only if the previous statement returns a custom type (another page object).

Important

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:

1const myModal = await utam.load(MyModalWithDynamicContent);
2
3// long version
4const footerArea = await myModal.getContent(FooterAreaWrapper);
5const footerButtonsPanel = await footerArea.getFooterButtonsPanel();
6await footerButtonsPanel.clickButtonByIndex(1);
7
8// short version
9await myModal.clickSave();

It’s possible to compose the long version into a short version using a chain.

1{
2  "elements": [
3    {
4      "name": "content",
5      "type": "container"
6    }
7  ],
8  "methods": [
9    {
10      "name": "clickSave",
11      "compose": [
12        {
13          // invoke container method with hardcoded type
14          "element": "content",
15          "args": [
16            {
17              "type": "pageObject",
18              "value": "my/pageObjects/FooterAreaWrapper"
19            }
20          ],
21          "returnType": "my/pageObjects/FooterAreaWrapper"
22        },
23        {
24          // invoke getter
25          "chain": true,
26          "element": "footerButtonsPanel",
27          "returnType": "my/pageObjects/FooterButtonsPanel"
28        },
29        {
30          // invoke public method
31          "chain": true,
32          "apply": "clickButtonByIndex",
33          "args": [
34            {
35              "value": 1
36            }
37          ]
38        }
39      ]
40    }
41  ]
42}

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
1{
2    "elements": [
3        {
4            "type": "my/pageObjects/tableCell",
5            "name": "tableCells",
6            "selector": {
7                "css": "table-cell",
8                "returnAll": true
9            }
10        }
11    ]
  • table JSON
1{
2  "elements": [
3    {
4      "type": "my/pageObjects/tableRow",
5      "name": "tableRows",
6      "selector": {
7        "css": "table-row",
8        "returnAll": true
9      }
10    }
11  ],
12  "methods": [
13    {
14      "name": "getAllCells",
15      "compose": [
16        {
17          "returnType": "my/pageObjects/tableRow",
18          "returnAll": true,
19          "element": "tableRows"
20        },
21        {
22          "chain": true,
23          "returnType": "my/pageObjects/tableCell",
24          "returnAll": true,
25          "element": "tableCells"
26        }
27      ]
28    }
29  ]
30}

Generated Java code:

1public final List<TableCell> getAllCells() {
2    List<TableRow> statement0 = this.getTableRowsElement();
3    List<TableCell> statement1 =
4        statement0
5            .stream()
6            .flatMap(element -> element.getTableCells().stream())
7            .collect(Collectors.toList());
8    return statement1;
9}

Compose Container Invocation 

A pageObject type parameter can be used to invoke a container method inside a compose statement.

1{
2  "elements": [
3    {
4      "name": "containerElement",
5      "type": "container"
6    }
7  ],
8  "methods": [
9    {
10      "name": "composeContainerHardcoded",
11      "compose": [
12        {
13          "element": "containerElement",
14          "args": [
15            {
16              "type": "pageObject",
17              "value": "utam-tests/pageObjects/myPageObject"
18            }
19          ],
20          "returnType": "utam-tests/pageObjects/myPageObject"
21        }
22      ]
23    },
24    {
25      "name": "composeContainer",
26      "compose": [
27        {
28          "element": "containerElement",
29          "args": [
30            {
31              "type": "pageObject",
32              "name": "pageObjectCtor"
33            }
34          ]
35        }
36      ]
37    }
38  ]
39}

Generated JavaScript code:

1// declaration
2composeContainerHardcoded(): Promise<unknown>;
3composeContainer<T extends _UtamBasePageObject>(pageObjectCtor: _PageObjectCtor<T>): Promise<unknown>;
4
5// implementation
6import _MyPageObject from 'utam-tests/pageObjects/myPageObject';
7
8async composeContainerHardcoded() {
9    const _result0 = await this.__getContainer(_MyPageObject);
10    return _result0;
11}
12
13async composeContainer(pageObjectCtor) {
14    const _result0 = await this.__getContainer(pageObjectCtor);
15    return _result0;
16}

Compose Entering Frame 

Frame and Root Page Object type parameters can be used to compose entering a frame:

1{
2  "elements": [
3    {
4      "name": "frameElement",
5      "type": "frame",
6      "selector": {
7        "css": "#frame"
8      }
9    }
10  ],
11  "methods": [
12    {
13      "name": "composeEnterFrameHardcoded",
14      "compose": [
15        {
16          "element": "document",
17          "returnType": "utam-tests/pageObjects/frameArea",
18          "apply": "enterFrameAndLoad",
19          "args": [
20            {
21              "type": "elementReference",
22              "value": "frameElement"
23            },
24            {
25              "type": "rootPageObject",
26              "value": "utam-tests/pageObjects/frameArea"
27            }
28          ]
29        }
30      ]
31    },
32    {
33      "name": "composeEnterFrame",
34      "compose": [
35        {
36          "element": "document",
37          "returnType": "rootPageObject",
38          "apply": "enterFrameAndLoad",
39          "args": [
40            {
41              "type": "frame",
42              "name": "frameElementParameter"
43            },
44            {
45              "type": "rootPageObject",
46              "name": "pageObjectCtor"
47            }
48          ]
49        }
50      ]
51    }
52  ]
53}

Generated JavaScript code:

1// declaration
2import _FrameArea from 'utam-tests/pageObjects/frameArea';
3
4composeEnterFrameHardcoded(): Promise<_FrameArea>;
5composeEnterFrame<T extends _UtamBaseRootPageObject>(frameElementParameter: _FrameUtamElement, pageObjectCtor: _PageObjectCtor<T>): Promise<T>;
6
7
8// implementation
9import _FrameArea from 'utam-tests/pageObjects/frameArea';
10
11async composeEnterFrameHardcoded() {
12    const _statement0 = await this.getDocument();
13    const _result0 = await _statement0.enterFrameAndLoad(await this.__getFrameElement(), _FrameArea);
14    return _result0;
15}
16
17async composeEnterFrame(frameElementParameter, pageObjectCtor) {
18    const _statement0 = await this.getDocument();
19    const _result0 = await _statement0.enterFrameAndLoad(frameElementParameter, pageObjectCtor);
20    return _result0;
21}