Design Page Objects

UTAM is based on the popular Page Object model design pattern commonly used in Selenium tests. The page object design pattern uses an object-oriented class to decouple a test from the target web page for robustness and easier test maintenance. Instead of using an object-oriented class to write a page object, a UTAM page object is authored in JSON, and described by an easy-to-understand UTAM grammar. These JSON page objects are easy to write and easy to read.

Any UI page can be expressed in UTAM page object grammar. Each web application page or component has a corresponding JSON page object that defines the UI interface for your tests. The UTAM compiler transforms the JSON page objects into JavaScript and Java page objects. You write your tests against the generated JavaScript or Java code. The JSON page object is the single source of truth for all your tests, no matter which testing language you use.

You can write your own page objects and use page objects created by other developers.

When you create a page object, start small and iterate. Think about the parts of the page that your test needs to exercise and model those parts in JSON. Think of the methods exposed in a page object as an API that covers the interactions and assertions that a test writer can exercise.

Writing a test and a page object from scratch is like preparing a meal. When you cook, you might want to marinade your protein and then prepare your vegetables before returning to cook the protein. With UTAM, you can start with the skeleton code for your test and then switch over to make sure that your page object exposes the interactions and assertions needed for the test. Continue to switch back and forth between the page object and test until the test is complete and it passes!

Create a JSON File

Create a declarative page object in a JSON file with the filename pageName.utam.json. We recommend using the filename componentName.utam.json.

The componentName.utam.json file can live anywhere in the component tree. We suggest putting componentName.utam.json in a folder named __utam__ next to the component’s .html, .js, and .css files.

src/
  ├── assets/
  ├── modules / example
     └── app/
     └── home/
         └── __utam__/
           └── home.utam.json
         ├── home.css
         └── home.html

Set Root Page Object Properties

To load a page object from a test, set root to true and add a selector element that points to its most outer (root) element. A root page object can be loaded directly inside the browser.

If a component can be loaded only inside its immediate parent (for example, a navigation item can be loaded only inside a navigation bar), don’t mark it as root. You can set set root to true for a non-root page object but if the page object is moved inside a shadow root, tests will break. To make page objects more durable, never treat a non-root page object as a root.

For example:

{
    "root": true,
    "selector": {
        "css": "one-app-nav-bar"
    }
}

For more information, see JSON Grammar: Root Element and Tutorials: Root Page Objects.

Add Elements

A JSON page object is a tree of elements that map to the DOM of the corresponding page. Define a nested structure of page object elements to expose the parts of the DOM that your test needs to access. To improve encapsulation and the robustness of a page object, declare a public method for a use case instead of setting an element as public. A public element makes it harder to evolve your page object in the future without breaking tests.

There are a few types of page object elements:

Add Public Methods

Define public compose methods that your test can call to interact with the page. A compose method combines interactions with basic elements into reusable abstractions for common use cases like login where we enter a username and password, and then click submit.

Use Explicit Waits

Use explicit waits in page objects to wait for certain actions to complete and yield until the conditions are met. Explicit waits help you to write more durable and less flappy tests.

For more information, see Explicit Waits.

Add Imperative Extensions

Imperative extensions enable you to create reusable utility code to address complex use cases that aren't supported in UTAM's JSON grammar. For example, you can't use the JSON grammar to navigate a data table where each column shows data from a different type of component with different markup.

Note: Imperative extensions are powerful because you can use the full capabilities of a programming language. However, we strongly discourage their usage except as a last resort because they violate the declarative nature of UTAM. If you use an imperative extension, you must implement and maintain it in each programming language that you use with UTAM.

For more information, see Imperative Extensions and JSON Grammar: Imperative Extensions.