Generator Rules

All generator rules are optional and can be set at the project level or next to component HTML files (starting with version 1.3.x), or both. If the generator finds a rules file in the same directory as an HTML source file whose name matches the pattern in therulesFileMask option, the generation parameters from the rules file override the default rules and rules defined in a generator configuration file.

<inputRootDir>
├── generator.config.json
├── src/
   └── modules/
      ├── component1/
         └── component1.html
         └── component1.rules.json

In this example, we set rules and runner parameters applicable to all source files in generator.config.json and adjust or override some rules at the component level in component1.rules.json.

Any runner or JSON generation parameter can be overridden. Runner parameters are explained in Generation runner parameters.

The generator traverses the tree of the HTML elements and produces the JSON output. The next sections explain all the configuration parameters responsible for how the generator interprets HTML and affects the generated JSON.

When a parameter is set in the configuration and in an element-related rules, the value is added to the default list. The value doesn't override the default list, it adds to it.

Each string value set in the configuration for elements can be a regular string (for a full match) or a regular expression for a partial match.

NOTE: All listed configuration rules are relevant since generator version 2.0.0

Public basic, container, and frame elements

A public basic element, container or frame is generated from an HTML tag if it has a certain tag name, a class attribute with a given value, a type attribute with a given value, or if an element has one of the listed attributes.

To ignore something that is configured to be included, use the optional excludeElements parameter.

Configuration parameter: publicElements

publicElements: {
        withTags: {
            input: ['actionable', 'clickable', 'editable'],
            button: ['actionable', 'clickable'],
            a: ['actionable', 'clickable'],
            area: ['actionable', 'clickable'],
            li: ['actionable', 'clickable'],
            option: ['actionable', 'clickable'],
            menuItem: ['actionable', 'clickable'],
            textarea: ['actionable', 'clickable', 'editable'],
            select: ['actionable', 'clickable', 'editable'],
            iframe: 'frame',
            td: [],
            th: [],
            title: [],
            slot: 'container',
        },
        withType: {
            button: ['actionable', 'clickable'],
            checkbox: ['actionable', 'clickable'],
            radio: ['actionable', 'clickable'],
            reset: ['actionable', 'clickable'],
            submit: ['actionable', 'clickable'],
        },
        withAttributes: {
            onclick: ['actionable', 'clickable'],
            ondblclick: ['actionable', 'clickable', 'editable'],
            onkeydown: ['actionable', 'editable'],
            onkeypress: ['actionable', 'editable'],
            onchange: ['actionable'],
        },
    }
``

For example for the following HTML:

```html
<div>
    <button type="button">Click Me!</button>
    <div onchange={handle}>test events</div>
</div>

The generator produces the following public elements:

"elements" : [
    {
        "public": true,
        "name": "button",
        "type": ["actionable", "clickable"],
        "selector": { "css": "button" }
    },
    {
        "public": true,
        "name": "div",
        "type": ["actionable"],
        "selector": { "css": "div" }
    },
]

Container element

According to default rules for public elements, a public container element is generated from a <slot> HTML element.

<template if:false={computedShowOutputField}>
    <span class="test-id__field-value slds-size_1-of-1">
        <slot name="inputField"></slot>
        <slot></slot>
    </span>
</template>

The generator produces the following public container element:

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

Configuration parameter: containerSelector

If the slot doesn't have a name attribute, it's the default slot, so the generated selector depends on the containerSelector parameter. The content of the slot isn't processed further because a container can't have child elements.

{
    "public": true,
    "name": "slot",
    "type": "container",
    "selector": { "css": ":scope > *:first-child" }
}

Frame element

According to default rules for public elements, a frame element is generated from an <iframe> HTML element.

<div class="frame">
    <iframe src="https://www.w3schools.com" title="W3Schools Free Online Web Tutorials"></iframe>
 </div>

The generator produces the following public frame element:

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

Custom elements

A custom element is generated from a custom HTML element. An HTML element is considered custom if its tag isn't part of the HTML5 standard and contains "-"; for example, <custom-element>.

<template>
    <my-custom-element>Custom content</my-custom-element>
</template>

The generator produces the following public custom element:

{
    "public": true,
    "name": "customElement",
    "type": "my-namespace/pageObjects/customElement",
    "selector": { "css": "my-custom-element" }
}

A generated element requires a type, so the generator splits a custom HTML tag by "-" and considers the first part a namespace (in our example, it's my). Then, the generator tries to find a proper type prefix for the given namespace looking at the following configuration parameters.

namespaces

First, the generator tries to find a match in the namespaces map. Let's say our config looks like this:

{
    "namespaces": {
        "my" : "my-namespace/pageObjects",
        "lightning" : "salesforce/lightning/pageObjects"
    }
}

Since the generator can find a map entry for our namespace prefix my, it picks the type and adds the name, so the type is my-namespace/pageObjects/component2.

defaultNamespace

If the namespaces map is empty or doesn't have a proper entry, the generator uses the preconfigured default namespace.

If there isn't a matching namespace, a custom element's type is utam/pageObjects/customElement.

Traverse content of a custom element

generateCustomContent

When the generator processes a custom HTML tag such as my-custom-tag, it can be treated as a separate HTML source to generate another UTAM page object. If this parameter is set to true and myComponent1.html contains a custom HTML tag such as <my-component2>some HTML here</my-component2>, the generator treats the content of the custom tag as another HTML source for generation and creates a component2.utam.json file.

<inputRootDir>
├── utam-generator.config.json
├── src/
   └── modules/
      ├── component1/
         └── component1.html
         └── __utam__/
            └── component1.utam.json
            └── component2.utam.json

ignoreCustomTags

For custom HTML elements with tags such as my-custom-tag, the default behavior is to generate a custom element and not traverse the body of the custom HTML element because it's considered a different component and a different page object.

Use ignoreCustomTags if you prefer to avoid a custom element and generate JSON for the body of a custom HTML element instead.

<div>
    <my-generated-component>
          <button id='number'></button>
    </my-generated-component>
</div>

By default, the generated JSON would be:

{
    "elements": [
        {
            "public": true,
            "name": "generatedComponent",
            "selector": {
                "css": "my-generated-component"
            },
            "type": "utam/pageObjects/generatedComponent"
        }
    ]
}

But if a configuration file includes "ignoreCustomTags" : ["my-generated-component"], the HTML content in the body of my-generated-component is traversed and an element is generated for the button. Here's the output:

{
    "elements": [
        {
            "public": true,
            "name": "generatedComponent",
            "selector": {
                "css": "my-generated-component"
            },
            "shadow": {
                "elements": [
                    {
                        "public": true,
                        "name": "customTest",
                        "type": "clickable",
                        "selector": {
                            "css": "button#number"
                        }
                    }
                ]
            }
        }
    ]
}

Scope elements

Some HTML elements are used to scope other elements. When an element is configured to be a scope element, the generator creates a private element.

scopeElements

scopeElements: {
        withTags: ['footer', 'form', 'header', 'label', 'section', 'table', 'tr', 'ul', 'ol', 'menu'],
        withClasses: [],
        withAttributes: ['id', 'name', 'slot'],
    },

Consider this HTML:

<table>
        <tr>
            <td>table cell</td>
        </tr>
</table>

The generator produces the following JSON, creating private scope elements for table and tr as configured. td isn't defined as a scope element so the generator creates a public tableCell element.

{
    "elements": [
        {
            "name": "table",
            "type": [],
            "selector": {
                "css": "table"
            },
            "elements": [
                {
                    "name": "tableRow",
                    "type": [],
                    "selector": {
                        "css": "tr"
                    },
                    "elements": [
                        {
                            "public": true,
                            "name": "tableCell",
                            "type": [],
                            "selector": {
                                "css": "td"
                            }
                        }
                    ]
                }
            ]
        }
    ]
}

Exclude elements from generated JSON

The excludeElements parameter is responsible for ignoring HTML elements for any type, basic, custom, or scope. It overrides publicElements and scopeElements.

excludeElements

excludeElements: {
        withTags: ['meta', 'script', 'head', 'tbody'],
        withClasses: [],
        withAttributes: []
    }

List elements

loopElements

loopElements: {
        withAttributes: ['for:each', 'for:item', 'for:iterator'],
        withTags: [],
        withClasses: []
    }

If an HTML element is located inside a loop element, it's marked as a list (the generated selector has returnAll: true).

<template for:each={filteredTodos} for:item="todo">
    <todo-item onupdate={handleTodoUpdate}></todo-item>
</template>

The generator produces the following public list element with "returnAll": true:

{
    "public": true,
    "name": "items",
    "type": "utam/pageObjects/item",
    "selector": { "css": "todo-item", "returnAll": true }
}

In addition, the generator adds a public element with an indexed selector:

{
    "public": true,
    "name": "itemByIndex",
    "type": "utam/pageObjects/item",
    "selector": {
        "css": "todo-item:nth-of-type(%d)",
        "args": [{ "name": "index", "type": "number"}]
    }
}

Some HTML elements with siblings that have the same HTML tag are also considered a list.

For example, repeated options in a select tag.

<select>
    <option>one</option>
    <option>two</option>
</select>

The generator produces the following output:

{
    "public": true,
    "name": "select",
    "type": ["actionable", "clickable", "editable"],
    "selector": { "css": "select" },
    "elements": [
        {
            "public": true,
            "name": "options",
            "type": ["actionable", "clickable"],
            "selector": { "css": "option", "returnAll": true }
        }
    ]
}

The content of the list elements isn't processed further because a list can't have child elements.

Override generated element names and selectors

The generated elements and methods are named based on the HTML tags and don't reflect the business function. The following configuration parameter enables you to rename elements by configuring key-value pairs where the key is the element name and the value is the replacement value for the generator.

elementNames

Here's an example of the configuration:

{
    "elementNames": {
        "span": "activateInlineEdit"
    }
}

In the generated JSON, a span element is renamed:

{
    "elements": [
        {
            // instead of "name" : "span"
            "name": "activateInlineEdit",
            "selector": ...
        }
    ]
}

elementSelectors

Element selectors are automatically generated using the id, name, or slot attributes, and the HTML tag. The generated element selector can be changed by configuring key-value pairs where the key is the element name (after renaming) and the value is the selector.

Here's an example of the configuration:

{
    "elementSelectors": {
        "myElement": {
            "css" : ".myElement"
        }
    }
}

In the generated JSON, the element will get a different selector:

{
    "elements": [
        {
            "name": "myElement",
            "selector": {
                "css" : ".myElement"
            }
        }
    ]
}

Marking page object as a root and changing root selector

rootSelectors

The generated JSON can be marked as a root if the root HTML element is one of: <html>, <form>, <body>. The root selector is the same as the root HTML element:

<html>
    <body>
        <!-- content -->
    </body>
</html>

Generated JSON:

{
    "root": true,
    "selector": {
        "css": "html"
    },
    "elements": []
}

rootSelector

This rule only makes sense for an individual HTML file. If the value is set, the generated page object will be marked as root and given the configured selector.

Here's an example configuration:

{
    "rootSelector" : {
        "css" : "my-root"
    }
}

This configuration produces the following JSON:

{
    "root": true,
    "selector": {
        "css": "my-root"
    }
}

Generate methods

To add compose methods to the generated JSON file, add methods to the generation rules. These methods follow the same syntax as a regular compose method in UTAM grammar.

methods

Here's an example of rules:

{
    "methods": [
        {
            "name": "getRootText",
            "compose": [
                {
                    "element": "root",
                    "apply": "getText"
                }
            ],
            "description": {
                "text": ["get record label text"]
            }
        }
    ]
}

This exact method is added to the generated JSON.

Rules for Root Description

A generated JSON page object can have a description object that includes the author and text properties set from the values in the configuration.

descriptionAuthor

rootDescription

If descriptionAuthor is set to "my team" and rootDescription is set to "my custom root description", this is the generated JSON:

{
    "description": {
        "author": "my team",
        "text": ["Page Object: <name of page object>", "my custom root description"]
    }
}

Shadow boundary

A <template> HTML element or custom HTML element is considered a shadow boundary. For example for following HTML snippets:

<template>
    <div class="wrapper">
        <!-- content -->
    </div>
</template>
<div>
    <my-generated-component>
          <button id='number'></button>
    </my-generated-component>
</div>

The generator produces a shadow boundary:

"shadow": {
     "elements": [
        { }
    ]
}

Traverse frame content

traverseFrameContent

If parameter is set to true, generator will traverse content of the frame and create a separate JSON file for the content. For example for the given HTML file 'my-html' with two nested frames:

<div class="content iframe-parent">
    <iframe id="outer">
        <html>
            <body>
                <custom-tag>something</custom-tag>
                <form>
                    <input>Outer frame</input>
                </form>
                <iframe id="inner">
                    <html>
                        <body>
                            <input>Inner frame</input>
                        </body>
                    </html>
                </iframe>
            </body>
        </html>
    </iframe>
</div>

If configuration parameter is set to true, the generator produces three files:

├── __utam__/
   └── my-html.utam.json
   └── my-html_irame.utam.json // for iframe with id="outer"
   └── my-html_irame_iframe.utam.json // for iframe with id="inner"