Skip to contents

elem_expect() waits for a set of conditions to return TRUE. If, after a certain period of time (by default 4 seconds), this does not happen, an informative error is thrown. Otherwise, the original element is returned.

elem_wait_until() does the same, but returns a logical value (whether or not the test passed), allowing you to handle the failure case explicitly.

Usage

elem_expect(x, ..., testthat = NULL, timeout = NULL)

elem_wait_until(x, ..., timeout = NULL)

Arguments

x

A selenider_element/selenider_elements object, or a condition.

...

<dynamic-dots> Function calls or functions that must return a logical value. If multiple conditions are given, they must all be TRUE for the test to pass.

testthat

Whether to treat the expectation as a testthat test. You do not need to explicitly provide this most of the time, since by default, we can use testthat::is_testing() to figure out whether elem_expect() is being called from within a testthat test.

timeout

The number of seconds to wait for a condition to pass. If not specified, the timeout used for x will be used, or the timeout of the local session if an element is not given.

Value

elem_expect() invisibly returns the element(s) x, or NULL if an element or collection of elements was not given in x.

elem_wait_for() returns a boolean flag: TRUE if the test passes, FALSE otherwise.

Conditions

Conditions can be supplied as functions or calls.

Functions allow you to use unary conditions without formatting them as a call (e.g. is_present rather than is_present()). It also allows you to make use of R's anonymous function syntax to quickly create custom conditions. x will be supplied as the first argument of this function.

Function calls allow you to use conditions that take multiple arguments (e.g. has_text()) without the use of an intermediate function. The call will be modified so that x is the first argument to the function call. For example, has_text("a") will be modified to become: has_text(x, "a").

The and (&&), or (||) and not (!) functions can be used on both types of conditions. If more than one condition are given in ..., they are combined using &&.

Custom conditions

Any function which takes a selenider element or element collection as its first argument, and returns a logical value, can be used as a condition.

Additionally, these functions provide a few features that make creating custom conditions easy:

  • Errors with class expect_error_continue are handled, and the function is prevented from terminating early. This means that if an element is not found, the function will retry instead of immediately throwing an error.

  • selenider functions used inside conditions have their timeout, by default, set to 0, ignoring the local timeout. This is important, since elem_expect() and elem_wait_until() implement a retry mechanic manually. To override this default, manually specify a timeout.

These two features allow you to use functions like elem_text() to access properties of an element, without needing to worry about the errors that they throw or the timeouts that they use. See Examples for a few examples of a custom condition.

These custom conditions can also be used with elem_filter() and elem_find().

See also

Examples

html <- "
<div class='class1'>
<button id='disabled-button' disabled>Disabled</button>
<p>Example text</p>
<button id='enabled-button'>Enabled</button>
</div>

<div class='class3'>
</div>
"
session <- minimal_selenider_session(html)

s(".class1") |>
  elem_expect(is_present)

s("#enabled-button") |>
  elem_expect(is_visible, is_enabled)

s("#disabled-button") |>
  elem_expect(is_disabled)

# Error: element is visible but not enabled
s("#disabled-button") |>
  elem_expect(is_visible, is_enabled, timeout = 0.5) |>
  try() # Since this condition will fail

s(".class2") |>
  elem_expect(!is_present, !is_in_dom, is_absent) # All 3 are equivalent

# All other conditions will error if the element does not exist
s(".class2") |>
  elem_expect(is_invisible, timeout = 0.1) |>
  try()

# elem_expect() returns the element, so can be used in chains
s("#enabled-button") |>
  elem_expect(is_visible && is_enabled) |>
  elem_click()
# Note that elem_click() will do this automatically

s("p") |>
  elem_expect(is_visible, has_exact_text("Example text"))

# Or use an anonymous function
s("p") |>
  elem_expect(\(elem) identical(elem_text(elem), "Example text"))

# If your conditions are not specific to an element, you can omit the `x`
# argument
elem_1 <- s(".class1")
elem_2 <- s(".class2")

elem_expect(is_present(elem_1) || is_present(elem_2))

# We can now use the conditions on their own to figure out which element
# exists
if (is_present(elem_1)) {
  print("Element 1 is visible")
} else {
  print("Element 2 is visible")
}

# Use elem_wait_until() to handle failures manually
elem <- s(".class2")
if (elem_wait_until(elem, is_present)) {
  elem_click(elem)
} else {
  reload()
}

# Creating a custom condition is easiest with an anonymous function
s("p") |>
  elem_expect(
    \(elem) elem |>
      elem_text() |>
      grepl(pattern = "Example .*")
  )

# Or create a function, to reuse the condition multiple times
text_contains <- function(x, pattern) {
  text <- elem_text(x)

  grepl(pattern, text)
}

s("p") |>
  elem_expect(text_contains("Example *"))

# If we want to continue on error, we need to use the
# "expect_error_continue" class.
# This involves making a custom error object.
error_condition <- function() {
  my_condition <- list(message = "Custom error!")
  class(my_condition) <- c("expect_error_continue", "error", "condition")
  stop(my_condition)
}

# This is much easier with rlang::abort() / cli::cli_abort():
error_condition_2 <- function() {
  rlang::abort("Custom error!", class = "expect_error_continue")
}

# This error will not be caught
try(elem_expect(stop("Uncaught error!")))

# These will eventually throw an error, but will wait 0.5 seconds to do so.
try(elem_expect(error_condition(), timeout = 0.5))
try(elem_expect(error_condition_2(), timeout = 0.5))