Explicit Waits
You can set timeout values in UTAM for implicit and explicit waits for Java and JavaScript.
Implicit and Explicit Timeouts
An implicit timeout is a time duration used to find an element inside its parent element or inside the driver for a root page object.
Note: Because we feel strongly that implicit timeout should be zero, as of version 1.0.0, implicit timeouts in UTAM are set to 0. Although we can't prevent usage of implicit timeouts and we still support the ability to override them, we strongly discourage their usage.
An explicit wait timeout is used for any waitFor
method inside UTAM framework, for example waitFor
is used inside compose
and beforeLoad
in JSON, or can be invoked on a page object in a test.
Explicit timeout duration depends on many factors such as application performance and network access to the environment where tests are being run. It should be set to the maximum reasonable duration for which a test should wait for the server to respond and for the browser to finish rendering the page. It can be as short as 5000 ms and as long as 90 seconds.
If timeouts aren’t set, the default for implicit timeouts is 0 and explicit timeouts is 20 seconds.
For more information on how implicit and explicit timeouts affect UI tests, see:
Many page objects and UI tests rely on large implicit timeout values to find an element that appears as the result of a user action. This approach can be problematic and can often lead to flappy tests due to changes in the testing environment or network delays. Instead, write more durable tests by waiting for certain actions to complete and yield until the conditions are met. Instead of waiting for an implicit time, wait for a condition to be ready (explicit wait) for the test to proceed.
Here are some examples of using explicit waits.
Wait for DOM Complete Status
Typically for root page objects that are loaded directly inside the driver (such as home pages, landing pages, or modals), it makes sense to override the "load" method to wait for DOM elements to finish loading (action waitForDocumentReady
):
{
"root": true,
"selector": { "css": "one-record-home" },
"beforeLoad": [
{
"element": "document",
"apply": "waitForDocumentReady"
},
{
"element": "root",
"apply": "isPresent"
}
]
}
Use Explicit Wait instead of a Retry
Consider this Java unit test code snippet that retries an attempt to click a button:
try {
button.click();
Thread.sleep(1000);
} catch (Exception e) {
button.click();
}
This code pattern usually means that the button isn't clickable initially and there’s a waiting period for an element to be in the state to accept such actions.
Here's a better pattern: wrap the click action into an explicit wait.
{
"elements" : [
{
"name": "button",
"type": "clickable",
"selector": { "css": ".not-yet-clickable" }
}
],
"methods" : [
{
"name" : "waitAndClick",
"compose" : [
{
"apply" : "waitFor",
"args" : [
{
"type": "function",
"predicate": [
{
"element": "button",
"apply" : "click"
}
]
}
]
}
]
}
]
}
Wait Before Assertion
Consider this Java unit test code snippet that repeatedly attempts to assert displayed text while the text is being loaded.
while(iterations < 10) {
try {
Thread.sleep(1000);
if(fieldWithText.getText().equals("my expected text")) { return; }
} catch (Exception e) {
iterations ++;
}
}
Instead of hardcoding retry and sleep steps in a loop, add a getLoadingText
method that accepts expected text as a parameter and uses an explicit wait to wait for the text to match.
{
"elements" : [
{
"name": "fieldWithText",
"selector": { "css": ".loading" }
}
],
"methods" : [
{
"name" : "getLoadingText",
"compose" : [
{
"apply" : "waitFor",
"args" : [
{
"type": "function",
"predicate": [
{
"element": "fieldWithText",
"apply" : "getText",
"matcher" : {
"type" : "stringEquals",
"args" : [
{
"name" : "expectedText",
"type" : "string"
}
]
}
}
]
}
]
}
]
}
]
}
Now the Java test code can look like this:
assert pageObject.getLoadingText("my expected text");
Wait for an Element's Presence
See examples in the guidelines about presence and nullable.
Wait for a Container Element
What if we need to wait for a container element? A container element is a placeholder for any internal component known only at runtime. Here's a container declaration:
{
"elements": [
{
"name": "panelContainer",
"type": "container",
"public": true
}
]
}
To wait for a container element to be loaded, we can use one of the following approaches:
- Add a
waitForAnyPanelContent
compose method that waits for any element inside a container usingcontainsElement
. This method is universal because it waits for any child element, so it's the preferred option. If waiting for the universal selector doesn't work for some reason, the page object needs to wait for a particular selector, as described in the example for the next approach.
{
"methods" : [
{
"name" : "waitForAnyPanelContent",
"compose" : [
{
"apply" : "waitFor",
"args" : [
{
"type": "function",
"predicate": [
{
"element": "root",
"apply" : "containsElement",
"args" : [
// this selector is same as container's default
// it waits for any child
{
"type": "locator",
"value" : { "css": ":scope > *:first-child" }
}
]
}
]
}
]
}
]
}
]
}
- Add a
waitForMyComponentPanelContent
compose method that waits for a particular selector using the samecontainsElement
basic action.
{
"methods" : [
{
"name" : "waitForMyComponentPanelContent",
"compose" : [
{
"apply" : "waitFor",
"args" : [
{
"type": "function",
"predicate": [
{
"element": "root",
"apply" : "containsElement",
"args" : [
// this selector is specific for MyComponent
{
"type": "locator",
"value" : { "css": "my-component" }
}
]
}
]
}
]
}
]
}
]
}
- If you need your method to return an instance of a particular page object inside a container, add a
waitForMyComponentPanelContent
compose method that invokes a container method wrapped in a wait with a hardcoded page object type. Then, the method will return an object of the given type:
{
"elements": [
{
"name": "panelContainer",
"type": "container"
}
],
"methods" : [
{
"name" : "waitForMyComponentPanelContent",
"compose" : [
{
"apply" : "waitFor",
"args" : [
{
"type": "function",
"predicate": [
{
"returnType": "utam/pageObjects/MyComponent",
"element": "panelContainer",
"args": [
{
"type": "pageObject",
"value": "utam/pageObjects/MyComponent"
}
]
}
]
}
]
}
]
}
]
}