Imperative Extensions

Imperative extensions enable you to create reusable utility code to address complex use cases that aren't supported in UTAM's JSON grammar. For example, the utility code is in exported named functions in JavaScript, and in static methods in Java.

Note: Imperative extensions are powerful because you can use the full capabilities of a programming language. However, we strongly discourage their usage except as a last resort because they violate the declarative nature of UTAM. If you use an imperative extension, you must implement and maintain it in each programming language that you use with UTAM.

In this tutorial, we create an imperative extension to navigate a data table. Each column in the table shows data from a different type of component so the markup is different. Click the DOM Tree Viewer to see the different types of components, such as example-basic-formatted-text and example-basic-formatted-url.

The test loads the root page object in shopsTable.utam.json and calls the getCellValueByIndex() method to retrieve the first data cell in the table.

const firstShopLabel = await shopsTable.getCellValueByIndex(1, 1);

The getCellValueByIndex() method is declared in shopsTable.utam.json and it invokes an imperative extension.

    "methods": [
        {
            "name": "getCellValueByIndex",
            "compose": [
                {
                    "returnType": "string",
                    "applyExternal": {
                        "type": "tutorial/shopsTableUtils",
                        "invoke": "getCellValueByIndex",
                        "args": [
                            {
                                "name": "rowIndex",
                                "type": "number"
                            },
                            {
                                "name": "columnIndex",
                                "type": "number"
                            }
                        ]
                    }
                }
            ]
        }
    ],

The applyExternal object declares an imperative extension and has these properties.

  • type is a required reference to a JavaScript module to import
  • invoke is a required reference to the method name to call
  • args is an optional array of arguments with name and type properties

Look at the shopsTableUtils module in the top-level panel in shopsTableUtils.utam.js. If you don't see it, minimize this text panel.

The getCellValueByIndex function in shopsTableUtils.utam.js contains the logic for the utility.

export async function getCellValueByIndex({ pageObject }, rowIndex, columnIndex) {
    ...
}

The first parameter is a context object with a single pageObject property that holds a reference to the current page object invoking the function. The remaining parameters match the args declared in applyExternal. In this example, the arguments identify the table cell by the row (rowIndex) and column (columnIndex) indexes.

The shopsTable.utam.json page object declares formattedTextByColumnIndex and formattedUrlByColumnIndex elements that match example-basic-formatted-text and example-basic-formatted-url components respectively.

The getCellValueByIndex function calls one or both of getFormattedTextByColumnIndex() and getFormattedUrlByColumnIndex() to retrieve the cell value identified by rowIndex and columnIndex.

The selector that uses the rowIndex and columnIndex arguments is defined in the parent cellByIndex element in shopsTable.utam.json.

For the sake of simplicity in this tutorial, getCellValueByIndex checks only for example-basic-formatted-text and example-basic-formatted-url elements. A complete implementation would also check for the components used in the other table columns. Remember that if you decide to write an imperative extension, you have to update and maintain it. With great power comes great responsibility!