Explicit Waits
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.
Grammar
We define an explicit wait by setting the apply
value to waitFor
, which is a public method that exercises a fluent wait for an element.
The wait wraps one or more functions and repeatedly invokes them within a timeout until they return a truthy value (boolean true
or any value that isn't null
or undefined
).
The function invoked inside waitFor
is defined in the args
array as a parameter for a method used in a compose statement:
type
is set tofunction
.predicate
is an array of compose statements that is invoked to test for the wait condition.
The predicate
value is a list of compose statements that will be executed until a truthy value is returned (no error, not null, and not false).
{
"apply": "waitFor",
"args": [
{
"type": "function",
"predicate": [
{
"element": "foo",
"apply": "click"
}
]
},
{
"value": "custom error message"
}
]
}
The second parameter "value" : "custom error message"
is optional and represents an error message that will be thrown if the action times out. By default, the error message is "Timeout after 20 seconds". A custom message makes the error more meaningful if a UI test fails.
Wait for an element
To wait for a particular element to be present, set wait
to true for the element. This shortcut generates a waitFor method for the element.
Load an element
For the load
method of the page object to wait for a particular element to be present, set load
to true for the element. This shortcut generates a waitFor method for the element that is invoked in the beforeLoad method. This is only applicable to elements without arguments or are not container elements.
Return type inside predicate
If a compose statement is a "waitFor"
action or an explicit wait, the return type is the return type of the last predicate statement. Similarly to a regular statement, the return type can be inferred, void, or set explicitly. Here are some examples.
- return type can be inferred because it's an element getter invocation, so this method returns the custom element type
{
"elements": [
{
"name": "custom",
"selector": {
"css": "my-custom-element"
},
"type": "my/custom/element"
}
],
"methods": [
{
"name": "returnCustomElement",
"compose": [
{
"apply": "waitFor",
"args": [
{
"type": "function",
"predicate": [
{
"element": "custom"
}
]
}
]
}
]
}
]
}
- return type is set explicitly, so this method returns a list or array of strings as specified in the last statement of the predicate
{
"elements": [
{
"name": "custom",
"selector": {
"css": "my-custom-element"
},
"type": "my/custom/element"
}
],
"methods": [
{
"name": "returnCustomElement",
"compose": [
{
"apply": "waitFor",
"args": [
{
"type": "function",
"predicate": [
{
"element": "custom"
},
{
"chain": true,
"apply": "customElementPublicMethod",
"returnType": "string",
"returnAll": true
}
]
}
]
}
]
}
]
}
Important: setting a
returnType
outside a predicate isn't allowed. This page object throws an error:
{
"methods": [
{
"name": "clickAndReturn",
"compose": [
{
"apply": "waitFor",
"args": [
{
"type": "function",
"predicate": [
{
"apply": "getString"
}
]
}
],
"returnType": "string"
}
]
}
]
}
Explicit wait timeout
You can set timeout values in UTAM for implicit and explicit waits for Java and JavaScript.
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:
Here are some examples of using explicit waits.
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 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 means 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"
}
]
}
]
}
]
}
]
}
]
}