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.

While imperative extensions are powerful, only use them as a last resort. They violate the declarative nature of UTAM and require you to individually implement and maintain them 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.

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.

The applyExternal property declares an imperative extension. For a full explanation of the syntax, see Grammar: imperative extensions.

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.

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.

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.

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.

The method implementation shouldn't interact with Driver or Element directly.

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 type
  • returnAll is a boolean that declares whether the return value is a list (true) or not (false). The default value is false. You must declare a return property if you declare returnAll.

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.

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 to void.
  • 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