Linting
UTAM provides the ability to statically analyze JSON files to prevent common bad practices, such as duplication of page objects or violations of the encapsulation pattern.
We encourage you not to disable any linting rules and to make sure that your page objects comply with recommended best practices.
Run linting
Linting is run during the page object generation process. Linting results are reported at the end of the generation process.
All warnings are posted in the console after the generation. For example:
WARNING:
linting warning 3001: page object test/lint/hasRootSelector: same root selector "By.cssSelector: root" is used as a root selector in the page object test/lint/hasSameRootSelector
linting warning 3002: page object test/lint/hasDifferentRootSelector: element "sameAsRootBasic" should have type "test.lint.HasRootSelector" because it uses its root selector
If at least one linting error occurs and the throwError
configuration parameter is set to true
(see the next section), the page object generator throws an exception with the list of all linting errors. For example:
utam.compiler.lint.UtamLintingError: UTAM linting failures:
linting error 2001: page object test/lint/defaultConfig: duplicate selector "By.cssSelector: :scope > *:first-child" for the elements "container2" and "container1"
linting error 2001: page object test/lint/defaultConfig: duplicate selector "By.cssSelector: .two" for the elements "three" and "two"
Configuring linting rules and violations
All linting rules and violation types (error or warning) are configurable as lintingOutputFile
and lint
properties in the compiler configuration.
If a compiler configuration doesn't have a lint
section, default values are applied.
{
"lintingOutputFile": "test.sarif.json",
"lint" : {
"throwError": false,
"<name of the rule>" : {
"violation" : "error",
"exclude" : []
},
"<name of the rule>" : {
"violation" : "error",
"exclude" : [
"my/test/component1",
"my/test/component2"
]
},
"<name of the rule>" : {
"violation" : "warning"
}
}
}
lintingOutputFile
String, default is "utam-lint.sarif"
. This parameter sets the path for a SARIF log file generated by the UTAM linter. The path is relative to the root of the project.
throwError
Boolean, default is false
. If set to true
, the linting process throws an error after reporting errors to the log.
<name of the rule>
is one of the rules listed in the next section.violation
warning
,error
, ordisabled
(disable the rule). The default value depends on the rule.exclude
SARIF report
Static Analysis Results Interchange Format (SARIF) is a common way to store linting results.
When UTAM generates page objects from JSON files, it creates utam-lint.sarif
in the SARIF JSON format, which looks like this:
{
"$schema" : "https://json.schemastore.org/sarif-2.1.0.json",
"version" : "2.1.0",
"runs" : [
{
"tool" : {
"driver" : {
"name" : "UTAM",
"semanticVersion" : "2.1.0",
"informationUri" : "https://github.com/salesforce/utam-java",
"rules" : [
{
"id" : "ULR01",
"name" : "Unique local selectors",
"shortDescription" : {
"text" : "Check for unique selectors inside the same file. By default, this is a warning because a list element can have the same selector."
}
}
// other rules
]
}
},
// all page objects being analyzed
"artifacts" : [
{
"description" : {
"text" : "page object utam/pageObjects/test"
},
"location" : {
"uri" : "src/test/resources/lint/changeDefaultConfig/test.utam.json",
"uriBaseId" : "%srcroot%",
"index" : 0
}
}
],
// violations
"results" : [
{
"ruleId" : "ULR02",
"message" : {
"text" : "root description is missing",
"id" : "2002"
},
"locations" : [
{
"physicalLocation" : {
"artifactLocation" : {
"uri" : "src/test/resources/lint/changeDefaultConfig/test.utam.json",
"uriBaseId" : "%srcroot%",
"index" : 0
},
"region" : {
"startLine" : 2
}
}
}
],
"fixes" : [
{
"description" : {
"text" : "add \"author\" property to the root description"
}
}
]
}
]
}
]
}
A file in SARIF format can be integrated with development tools such as GitHub to show linting rule violations:
GitHub documentation is here.
Linting rules
Note: Rules with error codes that start with 2 are applicable for one page object only. Rules with error codes that start with 3 are relevant for multiple page objects only, such as detecting duplicates from the earlier example.
duplicateSelectors
- Rule: page object elements in the same scope (with the same immediate parent) can't have duplicate selectors except if one of the elements is a list.
- How to fix: remove one or more duplicate elements.
- Error code: 2001
- Default violation type:
error
Example:
{
"elements": [
{
"name": "element1",
"selector": {
"css": "css"
}
},
{
// violation - same selector as element1
"name": "element2",
"selector": {
"css": "css"
},
"elements" : [
{
// not a violation - element has a different parent
"name": "element3",
"selector": {
"css": "css"
}
},
{
// not a violation - element is a list
"name": "element4",
"selector": {
"css": "css",
"returnAll" : true
}
}
]
}
]
}
requiredRootDescription
- Rule: it's a good practice to provide a description at the page object root level to explain the intended usage of the page object.
- How to fix: add a page object description.
- Error code: 2002
- Default violation type:
warning
Example:
{
// violation - description is empty
"elements" : []
}
requiredAuthor
- Rule: The page object description should include an author.
- How to fix: add an author property to the description.
- Error code: 2005
- Default violation type:
warning
Example:
{
// violation - description has no author
"description" : "description is a string"
}
{
// not a violation - author is provided
"description" : {
"text" : [
"description is a string"
],
"author" : "myTeam"
}
}
requiredMethodDescription
- Rule: it's a good practice to provide a description at the method level that explains the use cases. This rule isn't applied to elements; it's for compose methods only.
- How to fix: add a method description.
- Error code: 2003
- Default violation type:
warning
Example:
{
"description" : "root description here",
"methods" : [
{
// violation - description is empty
"name" : "doSomething",
"compose" : []
}
]
}
requiredSingleShadowRoot
- Rule: having more than one shadow boundary inside a page object usually means a violation of the encapsulation rule (1:1 mapping between a component and a page object) because the shadow root is always at the root of a different component. Any shadow boundary outside root is considered a violation.
- How to fix: create a new page object for the element that has a shadow root.
- Error code: 2004
- Default violation type:
warning
Example:
{
"elements" : [
{
"name" : "myChildComponent",
"selector" : {
"css" : "my-child-component"
},
// violation - myChildComponent should be a different page object
"shadow" : {
"elements" : [
]
}
}
]
}
duplicateRootSelectors
- Rule: two page objects with the same root selector usually means a violation of the encapsulation rule (1:1 mapping between a component and a page object).
- How to fix: remove the duplicate page object. Make sure that you keep all functionality.
- Error code: 3001
- Default violation type:
error
Example:
{
// first page object
"root": true,
"selector": {
"css": "my-root"
}
}
{
// second page object
"root": true,
"selector": {
// violation - same root selector
"css": "my-root"
}
}
elementCantHaveRootSelector
- Rule: if an element has the same selector as the root selector of a different page object, it should have the same type as the given page object.
- How to fix: add the proper type, which is the type of the page object with the root selector.
- Error code: 3002
- Default violation type:
error
Example:
{
// first page object my/test/Component
"root": true,
"selector": {
"css": "my-root"
}
}
{
// second page object
"elements" : [
{
// violation - element should have the proper type
"name" : "element1",
"selector": {
"css": "my-root"
}
},
{
// not a violation - element has the proper type
"name" : "element2",
"type" : "my/test/Component",
"selector": {
"css": "my-root"
}
}
]
}
duplicateCustomSelectors
- Rule: if an element in one page object has a custom type and a custom selector (with "-"), any element with the same selector in a different page object should have the same custom type.
- How to fix: change the element type to custom.
- Error code: 3003
- Default violation type:
error
Example:
{
// first page object
"elements" : [
{
"name" : "customElement",
"type" : "my/test/Component",
"selector": {
"css": "my-test-component"
}
},
{
"name" : "basicElement",
"selector": {
"css": ".myClass"
}
},
]
}
{
// second page object
"elements" : [
{
// violation - set type to "my/test/Component"
"name" : "basicElement1",
"selector": {
"css": "my-test-component"
}
},
{
// not a violation - basic elements can have the same selectors
"name" : "basicElement2",
"selector": {
"css": ".myClass"
}
}
]
}