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
- Type:
object
- Default:
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
- Type:
string
- Default:
:scope > *:first-child
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
- Type:
map
- Default:
empty or {}
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
- Type:
string
- Default:
"utam/pageObjects/"
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
- Type:
boolean
- Default:
false
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
- Type:
string array
- Default:
[]
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
- Type:
object
- Default:
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
- Type:
object
- Default:
excludeElements: {
withTags: ['meta', 'script', 'head', 'tbody'],
withClasses: [],
withAttributes: []
}
List elements
loopElements
- Type:
object
- Default:
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
- Type:
key-value pairs
- Default: undefined
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.
- Type:
key-value pairs
, value is a [selector object](/Selector Properties) - Default: undefined
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
- Type:
string array
- Default:
['form', 'html', 'body']
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
- Type:
string
- Default:
undefined
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
- Type: compose method
- Default: undefined
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
- Type:
string
- Default:
UTAM Generator
rootDescription
- Type:
string
- Default: undefined
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
- Type:
boolean
- Default:
false
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"