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, you can't use the JSON grammar to navigate a data table where each column shows data from a different type of component with different markup.
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.
To author an imperative extension:
- In JavaScript, use an ES module that exports a set of named functions.
- In Java, use a class with a set of static public methods.
The utility code is referenced from a page object's JSON file so that a test writer can call the code.
Reference Imperative Code from a Page Object
Declare an imperative utility method in a compose
statement in a page object. A page object can reference any number of utility classes.
This page object declares a compose method, myMethod
, that calls two utility methods, utilityA
and utilityB
. There's a separate applyExternal
statement for each utility method.
{
"methods": [
{
"name": "myMethod",
"return": "actionable",
"returnList": true,
"compose": [
{
"applyExternal": {
"type": "utam-impext/utils/impext/exampleUtils",
"invoke": "utilityA"
}
},
{
"applyExternal": {
"type": "utam-impext/utils/impext/exampleUtils",
"invoke": "utilityB",
"args": [
{
"name": "stringArg",
"type": "string"
}
]
}
}
]
}
]
}
The applyExternal
property declares an imperative extension. For a full explanation of the syntax, see Grammar: imperative extensions.
JavaScript Example
A JavaScript imperative extension must:
- Be an ES module (ESM) that exports a set of named functions.
- Have
.utam.js
as its file extension.
Additionally, the first parameter of each exported function must be a context
object that represents the context that an imperative extension requires to interact with the page object. The context
object holds a single pageObject
property, which is a reference to the page object.
This example implements the utilityA
and utilityB
functions declared in the page object.
// An imperative extension is authored as a JavaScript ES module.
// It exports a set of named functions.
// The first parameter of every function is a context object.
// It has a single pageObject property that holds a reference
// to the current page object in which the function has been referenced.
export async function utilityA(context) {
const { pageObject } = context;
// add logic for utility method here
}
// Destructure the context parameter to access the pageObject
// as alternative syntax with less code
export async function utilityB({ pageObject }, stringArg) {
// add logic for utility method here
}
The return value is based on what the last action (utilityB
) in the compose statement returns.
To call the myMethod
method declared in the page object from test code, use await context.pageObject.myMethod()
or await pageObject.myMethod()
if you use destructuring.
For an interactive JavaScript example, see this tutorial.
Java Example
To author a Java imperative extension, use a class with a set of static public methods.
The first parameter of each static public must be a context
object that's an instance of UtamUtilitiesContext
, which is responsible for passing the page object's context to the static method.
This example implements the utilityA
and utilityB
methods declared in the page object.
package utam.utils.impext;
import utam.examples.pageobjects.ExamplePageObject;
public class ExampleUtils {
public static void utilityA(UtamUtilitiesContext context) {
// Explicit casting to the page object type
ExamplePageObject examplePO =
(ExamplePageObject)context.getPageObject();
// call methods on page object here
}
public static List<Actionable> utilityB(UtamUtilitiesContext context,
String stringArg) {
// Explicit casting to the page object type
ExamplePageObject examplePO =
(ExamplePageObject)context.getPageObject();
// call methods on page object here
// return List<Actionable>
}
}
The UtamUtilitiesContext
object exposes the getPageObject()
getter that returns an object of type PageObject
. You must explicitly cast the type of the object returned by getPageObject()
to the type of the current page object instance, which is a subclass of the PageObject
type. In this example, the page object instance is ExamplePageObject
.
Note: The method implementation shouldn't interact with
Driver
orElement
directly.
Return Value
By default, the return value of a compose statement is the return value of the last action in the statement. The framework can infer the return value from the apply
value in the page object. The framework can't infer the return type of an imperative extension so you sometimes have to explicitly declare the return type if you write Java utility code.
A compose method has two optional properties where you can explicitly declare the return type.
return
is a string that declares the return typereturnAll
is a boolean that declares whether the return value is a list (true
) or not (false
). The default value isfalse
. You must declare areturn
property if you declarereturnAll
.
These properties are declared at the method level, which means they are siblings of the name
and compose
properties. The page object for our example declares that the method returns a list of actionable elements.
{
"methods": [
{
"name": "myMethod",
"return": "actionable",
"returnAll": true,
"compose": [ ...
]
}
]
}
For JavaScript, you don't have to declare the return
or returnAll
properties because the default behavior suffices. However, for Java, you must declare the return
property if the last action in the compose statement invokes utility code that has a non-void return type.
In Java, if the last action in the compose statement invokes utility code, these rules apply for the method's return type:
- If the
return
property is absent, the return type defaults tovoid
. - If the
return
property is declared, this value is set for the return type.
A utility method should return a type that can be referenced from JSON, which is one of:
void
- A primitive type (string, number, boolean) or a list or an array of primitives
- Another page object or a list or an array of page objects
- A basic element (actionable, editable, clickable) or a list or an array of basic elements