Page Object Elements

Elements can have different types.

Element types

If a component has an HTML element that you need in the page object, declare an element as an object in an elements array. You can put an element object in an elements array at the root, inside a shadow, or nested inside a basic element.

These are the element types:

An element has properties.

There are limitations for different elment types:

Basic Elements

A basic element can have these properties:

{
    "elements": [
        {
            "name": "myElement",
            "type": ["clickable", "editable"],
            "public": true,
            "selector": {
                "css": ".element"
            },
            "elements": []
        }
    ]
}

If an element has one basic type only, it can be defined as a string instead an array of strings. For example, this root element has the actionable type only:

{
    "exposeRootElement": true,
    "type": "actionable"
}

UTAM generates a public method that returns an instance of the element to interact with.

Java:

public MyElementElement getMyElement() {
    // return element
}

JavaScript:

//declaration
getMyElement(): Promise<_BaseUtamElement>;

// implementation
async getMyElement() {
    const driver = this.driver;
    const root = await this.getRootElement();
    let element = await _utam_get_myElement(driver, root, );
    return new _ActionableUtamElement(driver, element);
}

Custom Elements

A custom element has the same properties as a basic element, except that the type property is required and must reference another page object.

This example declares a custom element called todo-item, which lives in the utam-tutorial package.

{
    "root": true,
    "selector": { "css": "body" },
    "elements": [
        {
            "name": "todoApp",
            "type": "utam-tutorial/todoApp",
            "selector": { "css": "example-todo-app" },
            "public": true,
            "shadow": {
                "elements": [
                    {
                        "name": "todoItem",
                        "selector": { "css": "example-todo-item" },
                        "public": true,
                        "type": "utam-tutorial/todoItem"
                    }
                ]
            }
        }
    ]
}

Elements should only be nested inside custom element if it mimics component code. In most cases custom element only references another page object type and everything is encapsulated inside the page object.

The generated getTodoItem() method returns an object scoped inside its parent element.

JavaScript:

getTodoApp(): Promise<_ActionableUtamElement>;
getTodoItem(): Promise<_todoItem>;

Container Elements

There are certain requirements to declare a container element for a slot. For more information, see the guidelines in the slots guide.

Let's look at an example where we would need to declare a container element. For example, here is a component that has a placeholder for content in an output field. In component source code, a placeholder is a slot.

<template>
    <div class="slds-form-element__control">
        <span
            class={computedOutputFieldContainerClass}
            oninlineeditbehavioroverride={handleInlineEditBehaviorOverride}>
            <!-- slot for a field -->
            <slot name="outputField"></slot>
        </span>
    </div>
</template>

In this case, declare an element with "type": "container". The compiler generates a method with a parameter for the type of the component being loaded.

A container element has these properties:

  • As with any other element, if there's a #shadow-root between the basic element and the nested container element, enclose the container element in a shadow object. Note, that slots are usualy not inside shadow root.
  • Most containers would not have a hardcoded selector because content is unknown, except for named slots. If a selector is omitted, a default CSS selector of :scope > *:first-child will be used in the generated code.

For our example, the container element is:

{
    "elements": [
        {
            "name": "outputFieldContent",
            "type": "container",
            "public": true,
            "selector": {
                "css": "[slot='outputField']"
            }
        }
    ]
}

UTAM generates this JavaScript code.

async function _utam_get_outputFieldContent(driver, root) {
    let _element = root;
    const _locator = core.By.css("[slot='outputField']");
    return _element.findElement(_locator);
}

async getOutputFieldContent(ContainerCtor) {
        const driver = this.driver;
        const root = await this.getRootElement();
        let element = await _utam_get_outputFieldContent(driver, root, );
        element = new ContainerCtor(driver, element);
        return element;
}

From the test, we can load any page object inside our container.

const textFieldPageObject = await myPageObject.getOutputFieldContent(TextField);

If the selector inside the container is marked with "returnAll": true, the generated container method returns an array (in JavaScript) or a list (in Java) of objects.

Elements nested inside container

Container can have nested elements if it mimics component code, for example when source component provides default content of the slot. In most cases container does not have nested elements.

For example if component has following code that puts default content inside slot:

<template if:false={showThankYou}>
    <slot>
        <div class="form-icon">
            <lightning-icon icon-name="utility:form"></lightning-icon>
        </div>
    </slot>
</template>

then UTAM Page Object can have following nested element:

{
    "name": "content",
    "type": "container",
    "public": true,
    "elements": [
        {
            "name": "utilityIcon",
            "selector": { "css": "lightning-icon" },
            "public": true,
            "type": "utam-lightning/pageObjects/icon"
        }
    ]
}

Frame Elements

To load a frame or an iframe, use a frame element with the following properties:

Here's an example of a frame element:

{
    "name": "myPublicFrame",
    "public": true,
    "type": "frame",
    "selector": {
        "css": "iframe"
    }
}

The generated code returns an object of the special FrameElement type that can be used as a parameter in methods to switch between frames.

Wait for an element

If an element has wait set to true, UTAM generates a public method that waits for the element with the name waitFor<ElementName>. The method uses predicate syntax and wraps the invocation of the element getter into a fluent wait.

For example for the following element:

{
    "name": "mySlowElement",
    "selector": {
        "css": ".slow"
    },
    "wait": true
}

The generated JavaScript code is:

async waitForMySlowElement() {
    const _result0 = await this.waitFor(async () => {
        const _result0 = await this.__getMySlowElement();
        return _result0;
    });
    return _result0;
}

If marked as public, the element will have both public getter and waitFor methods.

Load an element

If an element has load set to true, UTAM generates a private method that waits for the element with the name waitFor<ElementName>. The method uses predicate syntax and wraps the invocation of the element getter into a fluent wait. This wait method is then automatically invoked in the page object's beforeLoad method.

For example for the following element:

{
    "name": "mySlowElement",
    "selector": {
        "css": ".slow"
    },
    "load": true
}

The generated JavaScript code is:

async __waitForMySlowElement() {
    const _result0 = await this.waitFor(async () => {
        const _result0 = await this.__getMySlowElement();
        return _result0;
    });
    return _result0;
}

async __beforeLoad__() {
    await this.__waitForMySlowElement();
}

Note: