Guide for Java Developers

This document explains how to configure the UTAM compiler, generate UTAM Java page objects, and use them in a UI test. Examples of tests and setup are available in the Java recipes repo.

If you write mobile UI tests using Appium, see these additional steps to set up your environment.

Compiler setup

We use Maven for page object generation as part of the build process. You can use an alternative tool rather than Maven if you prefer. We don't mandate a particular version of the Maven plugins. Use the plugin versions that work best for your setup.

There are two main phases in the generation of UTAM page objects:

  1. Delete the previously generated page objects if they exist (cleanup phase)
  2. Create new files with generated page objects (generation phase)

An example of a pom.xml file that's configured to first delete previously generated page objects and then generate new page objects can be found in the Java recipes repo.

Prerequisites

We require Java version 11 or higher.

If you're using a Java version higher than version 11, follow these steps as a prerequisite for generating UTAM page objects:

  1. In the directory containing your Maven pom.xml file, create a directory called .mvn.
  2. In that directory, create a file called jvm.config with the following contents:
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED

This file is required for Java versions greater than 11 to generate UTAM page objects, but optional for version 11. Note carefully that to use previously compiled UTAM page objects, this step is not required. An example of this directory and file can be found in the Java recipes repo.

Cleanup phase

To remove the previously generated page objects, the example uses maven-clean-plugin. The plugin configuration defines which files need to be deleted relative to the project root.

          <filesets>
            <fileset>
              <directory>src/main/java/utam</directory>
              <includes>
                <include>**</include>
              </includes>
              <excludes>
                <exclude>**/utils/**/*.java</exclude>
              </excludes>
              <followSymlinks>false</followSymlinks>
            </fileset>
          </filesets>

Remember to remove both generated page objects and imperative utilities if you use them.

Generation phase

To run the compiler and generate new page objects, the example uses exec-maven-plugin with two mandatory arguments:

The page object generation runner is implemented in the utam.compiler.EntryPoint class that's part of the utam-compiler package.

To configure the Java compiler, create a JSON configuration file and fill the required properties with appropriate settings for your project. Those properties don't have a default fallback value in Java. Enter values for those properties in the configuration file before running the compiler.

Note: We recommend utam.config.json as the name of the compiler configuration file.

Format of the compiler configuration JSON file

Here's an example of a valid configuration for the Java compiler.

{
    "pageObjectsRootDir": "/src/main/resources/spec",
    "pageObjectsFileMask": "(.*)\\.utam\\.json$",
    "pageObjectsOutputDir": "/src/main/java",
    "resourcesOutputDir": "/src/main/resources",
    "unitTestsOutputDir": "/src/test/java",
    "unitTestsRunner": "junit",
    "namespaces": [],
    "version": null,
    "copyright": null,
    "module": null,
    "profiles": null,
    "lintingOutputFile": "utam-lint.sarif",
    "lint": null
}

This section lists all the options supported by the Java compiler. For each option, we describe its JSON type, its default value, if it's required or not, and what it does.

By default, generated Java page objects are in the utam.pageobjects Java package. You can override the default package name by setting the namespaces option.

In Java, you must pass a --compilerRoot runtime argument to the compiler to set the root directory for the compiler, against which all paths are resolved.

pageObjectsRootDir

The path to the root directory containing JSON page objects. The compiler looks recursively in the root directory for declarative JSON page objects and extensions.

pageObjectsFilesMask

The file mask pattern used by the compiler to find input JSON page objects. It tells the compiler where to find the source JSON page objects relative to the root directory. Declare the file mask as a regular expression.

pageObjectsOutputDir

The relative path from the root directory to the target directory for generated page objects. The compiler generates compiled Java classes from the JSON page objects and puts them in the output directory.

resourcesOutputDir

The target folder for generated resources, such as profile configurations.

unitTestsOutputDir

The root folder where unit tests are generated.

unitTestsRunner

The type of unit test runner. Used to generate unit tests that target that specific runner.

namespaces

namespaces is a list of mappings between a folder in pageObjectsRootDir and the package name prefix for generated page objects. This option allows you to generate different Java packages for JSON page objects in different folders. By default, generated Java page objects are in the utam.pageobjects Java package.

A mapping consists of a pathMatch and a typeMatch.

Let's look at an example configuration file.

{
    "pageObjectsRootDir": "/src/main/resources/spec",
    "pageObjectsFileMask": "(.*)\\.utam\\.json$",
    "pageObjectsOutputDir": "/src/main/java",
    "namespaces": [
        {
            "typeMatch": "utam-lightning",
            "pathMatch": ".*/lightning"
        },
        {
            "typeMatch": "utam-custom",
            "pathMatch": ".*/custom"
        }
    ]
}

A JSON page object in /src/main/resources/spec/lightning/input.utam.json generates a utam.lightning.pageobjects.Input Java page object.

A JSON page object in /src/main/resources/spec/custom/input.utam.json generates a utam.custom.pageobjects.Input Java page object.

version

Version is an optional string that is propagated as @version in the generated page object Javadoc. For example, if the version is set:

{
    "version": "Spring '22"
}

The generated Javadoc in the code is:

/**
 * ...
 * @version Spring '22
 */

If the version isn't set, the current date and time is used:

/**
 * ...
 * @version 2022-12-01 09:12:01
 */

copyright

An optional array of strings that is added at the top of the generated page object. If the copyright is set:

{
    "copyright": ["Copyright (c) 2022, salesforce.com, inc.", "All rights reserved."]
}

The generated Javadoc in the code is:

/**
 * Copyright (c) 2022, salesforce.com, inc. All rights reserved.
 */
package pageobjects;

public interface MyPageObject extends PageObject {
  //....
}

module

The name of the module used to generate injections config. It's a mandatory configuration parameter if the UTAM compiler generates page objects for interfaces.

profiles

Used in injections config. See interfaces.

lintingOutputFile

If linting is enabled, the SARIF report is generated after compilation and written to this file.

lint

The object for the linting configuration. See linting.

interruptCompilerOnError

By default, the compiler interrupts execution if a compilation error occurs. If this parameter is set to false, the compiler continues to generate other page objects, combines all compilation errors into one report, and throws an error at the end. All the errors are added to the utam.errors.txt file and printed to the console.

Test setup

After you generate UTAM page objects, you can create a UI test that uses the page objects. Before your test can interact with the UI, UTAM needs to have access to a driver and to certain configuration parameters.

For Java, configure WebDriver in code by first creating an instance of UtamLoaderConfig. Set all configuration parameters, such as [implicit and explicit timeouts] (/grammar/explicit-waits#explicit-wait-timeout) on UtamLoaderConfig. Then, create an instance of UtamLoader, which is the object that can create UTAM page objects.

Add this code in a test class or in a super class or utility class that you can call from a test.

import java.time.Duration;
import utam.core.framework.consumer.UtamLoader;
import utam.core.framework.consumer.UtamLoaderConfig;
import utam.core.framework.consumer.UtamLoaderConfigImpl;
import utam.core.framework.consumer.UtamLoaderImpl;
import utam.core.selenium.factory.WebDriverFactory;

// ...

  UtamLoaderConfig config = new UtamLoaderConfigImpl();
  // configure timeouts
  config.setImplicitTimeout(Duration.ofSeconds(0));
  config.setExplicitTimeout(Duration.ofSeconds(60));

  // webDriver is an instance of org.openqa.selenium.WebDriver
  UtamLoader loader = new UtamLoaderImpl(config, webDriver);

For an example using this configuration code, see the utam-java-recipes repo.

Now, a test can create UTAM page objects by calling the UtamLoader.load() method.

An instance of the WebDriver can be created in the same class as the configuration code or elsewhere. UTAM provides a utility that can create an instance of the driver. Here's an example for Chrome:

import org.openqa.selenium.WebDriver;
import utam.core.driver.DriverType;
import utam.core.selenium.factory.WebDriverFactory;

// ...

  System.setProperty("webdriver.chrome.driver",  "my path to chromedriver here");
  WebDriver driver = WebDriverFactory.getWebDriver(DriverType.chrome);

Set up injection config for interfaces

If your UI tests use UTAM interfaces, you need to set up injection configs.

Find injection configs in consumed modules

Injection config is generated with page objects. It can be located:

Add injection configs to UtamLoader

To cover all consumed modules, our config should be:

{
    "injectionConfigs": [
        "vanilla-js-components/profile.config.json",
        "utam-salesforceapp-pageobjects.config.json",
        "ui-utam-pageobjects.config.json",
        "ui-spec.config.json",
        "base-components.config.json"
    ]
}
config = new UtamLoaderConfigImpl("utam.loader.json");

Set active profile

Setting the active profile affects which implementing class is picked. A common example is mobile tests, which need to set the current platform.

config.setProfile(MobilePlatformType.fromDriver(driver));

After the profile is set, UtamLoader knows how to pick up the right implementation based on the dependency injections config utam-salesforceapp-pageobjects.config.json, which looks like this:

{
    "platform": {
        "ios_tablet": [
            {
                "interface": "utam.pageObjects.auth.AddConn",
                "implementation": "utam.pageObjects.auth.AddConnIOSTabletImpl"
            }
        ]
    }
}

This particular config means that if the active profile is "ios_tablet", and UtamLoader.load(AddConn.class) is invoked, the loader should instantiate the AddConnIOSTabletImpl class.

Mobile setup

To execute a test using a UTAM page object against a local simulator or emulator, the UTAM framework needs to know your local environment through system properties for Java or a wdio configuration file for JavaScript.

These properties are common for both iOS and Android platforms:

System.setProperty("nodejs", "/usr/local/bin/node");
System.setProperty("appium", "/usr/local/lib/node_modules/appium/");
System.setProperty("app.bundleid", "com.salesforce.chatter");

For iOS:

System.setProperty("ios.device", "iPhone 8 Plus");
System.setProperty("ios.app", "<path to iOS test app>");

For Android:

System.setProperty("android.app", "<path to Android test app>");
// initial activity (app.activity), Android only
System.setProperty("app.activity", "com.salesforce.chatter.Chatter");

Appium setup

For Java, a test must use AppiumServerFactory to start an Appium server and WebDriverFactory to start an Appium session using the Appium server.

appiumService = AppiumServerFactory.getAppiumServer()

// For iOS:
driver = WebDriverFactory.getWebDriver(DriverType.ios, appiumService, desiredCapabilities);

// For Android:
driver = WebDriverFactory.getWebDriver(DriverType.android, appiumService, desiredCapabilities);

Write UI tests

See examples in the Java recipes repo.

Code completion (IntelliSense) for UTAM JSON files

The JSON schema for UTAM page objects is available at Schema Store.

Most source code editors or IDEs support code completion (IntelliSense) for .utam.json file extensions either automatically or through configuration.

Example automatic schema mapping by IntelliJ IDEA

In IntelliJ IDEA, no configuration is needed and the IDE automatically identifies schema from Schema Store for .utam.json files as shown below.

IntelliJ IDEA screenshot

Example configuration for Eclipse

In Eclipse, add a catalog entry for the JSON schema.

Eclipse JSON Schema configuration