Nullable Versus isPresent

Consider these two cases that validate the absence of an element:

  1. Log in to an application with minimal access rights and check that a "Setup" button isn't present.
  2. After entering login credentials, check that the login form is no longer on the screen.

The key difference is that for the first use case, the button element was never in the DOM. We call it a nullable element. For the second use case, the login form element was present in the DOM but no longer is. We call it a stale element.

The following sections provide examples for both types of use cases.

When a test invokes an element's getter method, the framework attempts to find the element and return its value. In the first use case, a test invokes the getter for the Setup button.

Unless the element is marked as nullable, an error is thrown if the element isn't found.

If the element is marked as nullable, the getter returns null if the element isn't found.

A test or an imperative extension can check or assert a getter's returned value for null and act accordingly.

JavaScript example:

Java example:

If the elements weren't marked as "nullable": true, the page.getSetupButtonHTMLElement() or page.getSetupButtonCustomElement() getters would throw an exception if the element isn't present.

The nullable behavior is the same for a list of elements.

A test or an imperative extension can check or assert a getter's returned value for null and act accordingly.

JavaScript example:

Java example:

For better encapsulation, keep the element private and declare a compose method to check that its getter returns not null:

Note the syntax in the compose method. We used element and omitted apply to call the getter method.

The generated isSetupButtonPresent method calls the getter for the setupButton element, checks if its value isn’t null, and returns a boolean value.

A test or an imperative extension can assert or check the element's presence and act accordingly.

Another option is to check for an element's presence using the containsElement action first and then act accordingly.

Example of usage from a test:

  • JavaScript
  • Java

A getMyElement().isPresent() call returns true for an existing element or throws an error for a non-nullable absent element. The isPresent() call returns false when an element becomes absent or stale because the page was refreshed.

The isPresent call returns false when the form element became stale. Consider our second use case earlier where the login form is no longer on the screen after the login is complete.

Remember that invoking an element's getter triggers finding the element inside its parent. To use isPresent, save an instance of the element before it becomes stale to avoid an error that the element isn't found when its getter is invoked. The myForm = homePage.getForm() calls saves the form before it becomes stale.

If an element is marked as nullable, a test should check for a null value before calling any method, including isPresent, on it.

If an element was present but it takes time for it to become absent, use waitForAbsence. For example, an application might do some processing after we save changes in a form but doesn't hide the form immediately.

  • JavaScript example
  • Java example

If a test must wait for a certain element to be present inside a page object root, it's a best practice to make the wait a part of loading the page object by overriding the page object's load method.

A page Object is "loaded" when a test either call UtamLoader.load(Class) or invokes a getter method that returns the page object from inside its enclosing page object.

In this example, the page object overrides page loading by waiting for an element with a given selector using the containsElement action:

We can't use a nullable element in this example. A beforeLoad statement can invoke methods only against root or a document object because the page isn't fully loaded yet.

The predicate in the waitFor statement has to return a truthy value (not null, not false and doesn’t throw) for the method to complete without an error.

In our example, the root element should contain an element with the given selector inside its shadowRoot.

In some cases, it can take some time for an element to render. Such a wait isn't always allowed in a beforeLoad override such as in the previous example. For example, the element might not be inside root or its loading might not be stateless.

The predicate in the waitFor statement has to return a truthy value (not null, not false and doesn’t throw) for the method to complete without an error.

In this example, the method repeatedly calls the slowElement getter until it stops throwing an error that element isn't found or until a timeout is reached.

To wait for a list of elements, we can use the same approach as in the previous example. The only exception is that the element is marked with returnAll. This example also uses a custom type:

As in the previous example, this method repeatedly calls the slowCustomElements getter until it stops throwing an error that the element isn't found or until a timeout is reached.