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 importinvoke
is a required reference to the method name to callargs
is an optional array of arguments withname
andtype
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!