// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_BASE_INTERACTION_INTERACTIVE_TEST_H_
#define UI_BASE_INTERACTION_INTERACTIVE_TEST_H_

#include <concepts>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>

#include "base/functional/callback_helpers.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/rectify_callback.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_specifier.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/interaction/interaction_sequence.h"
#include "ui/base/interaction/interaction_test_util.h"
#include "ui/base/interaction/interactive_test_definitions.h"
#include "ui/base/interaction/interactive_test_internal.h"
#include "ui/base/interaction/polling_state_observer.h"
#include "ui/base/interaction/state_observer.h"

#if !BUILDFLAG(IS_IOS)
#include "ui/base/accelerators/accelerator.h"
#endif

namespace ui::test {

// Provides basic interactive test functionality.
//
// Interactive tests use InteractionSequence, ElementTracker, and
// InteractionTestUtil to provide a common library of concise test methods. This
// convenience API is nicknamed "Kombucha" (see
// //chrome/test/interaction/README.md for more information).
//
// This class is not a test fixture; it is a mixin that can be added to an
// existing test fixture using `InteractiveTestMixin<T>` - or just use
// `InteractiveTest`, which *is* a test fixture.
//
// Also, since this class does not implement input automation for any particular
// framework, you are more likely to want e.g. InteractiveViewsTest[Api|Mixin]
// or InteractiveBrowserTest[Api], which inherit from this class.
class InteractiveTestApi {
 public:
  InteractiveTestApi();
  virtual ~InteractiveTestApi();
  InteractiveTestApi(const InteractiveTestApi&) = delete;
  void operator=(const InteractiveTestApi&) = delete;

 protected:
  using InputType = InteractionTestUtil::InputType;
  using MultiStep = internal::InteractiveTestPrivate::MultiStep;
  using StepBuilder = InteractionSequence::StepBuilder;
  using TextEntryMode = InteractionTestUtil::TextEntryMode;
  using OnIncompatibleAction =
      internal::InteractiveTestPrivate::OnIncompatibleAction;
  using AdditionalContext = internal::InteractiveTestPrivate::AdditionalContext;
  using ElementSpecifier = ::ui::ElementSpecifier;

  // Construct a single MultiStep from one or more StepBuilders and/or
  // MultiSteps. This should only be necessary when packaging up steps in custom
  // verbs, or when appending a sequence of steps to another sequence.
  //
  // Note that you can use += to append one or more steps to the end of a
  // `MultiStep`.
  //
  // Simple example in a custom verb:
  // ```
  //  auto MyCustomVerb() {
  //    return Steps(
  //        DoStep1(),
  //        DoStep2(),
  //        DoStep3());
  //  }
  // ```
  //
  // Example with concatenation:
  // ```
  //  auto MyCustomVerb(bool do_third_step) {
  //    auto steps = Steps(
  //        DoStep1(),
  //        DoStep2());
  //
  //    if (do_third_step) {
  //      steps += DoStep3();
  //    }
  //
  //    steps += Steps(
  //        DoStep4(),
  //        DoStep5());
  //
  //    AddDescriptionPrefix(steps, "MyCustomVerb()");
  //    return steps;
  //  }
  // ```
  template <typename... Args>
    requires(internal::IsValueOrRvalue<Args> && ...)
  static MultiStep Steps(Args&&... args);

  // Returns an interaction simulator for things like clicking buttons.
  // Generally, prefer to use functions like PressButton() to directly using the
  // InteractionTestUtil.
  InteractionTestUtil& test_util() { return private_test_impl_->test_util(); }

  // Runs a test InteractionSequence in `context` from a series of Steps or
  // StepBuilders with RunSynchronouslyForTesting(). Hooks both the completed
  // and aborted callbacks to ensure completion, and prints an error on failure.
  template <typename... Args>
    requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
  bool RunTestSequenceInContext(ElementContext context, Args&&... steps);

  // Runs a test InteractionSequence from a series of Steps or StepBuilders with
  // RunSynchronouslyForTesting(). Hooks both the completed and aborted
  // callbacks to ensure completion, and prints an error on failure. The context
  // will be pulled from `context_widget()`.
  template <typename... Args>
    requires(sizeof...(Args) > 0 &&
             (ui::test::internal::IsValueOrRvalue<Args> && ...))
  bool RunTestSequence(Args&&... steps);

  // Convenience methods for creating interaction steps of type kShown. The
  // resulting step's start callback is already set; therefore, do not try to
  // add additional logic. However, any other parameter on the step may be set,
  // such as SetMustBeVisibleAtStart(), SetTransitionOnlyOnEvent(),
  // SetContext(), etc.
  //
  // Note that `ActivateSurface()`, `SelectMenuItem()` and
  // `SelectDropdownItem()` are not outside of interactive tests (e.g.
  // interactive_ui_tests); the exception is `SelectDropdownItem()` with the
  // default `input_type`, which programmatically sets the value rather than
  // using the actual drop-down.
  [[nodiscard]] StepBuilder PressButton(
      ElementSpecifier button,
      InputType input_type = InputType::kDontCare);
  [[nodiscard]] StepBuilder SelectMenuItem(
      ElementSpecifier menu_item,
      InputType input_type = InputType::kDontCare);
  [[nodiscard]] StepBuilder DoDefaultAction(
      ElementSpecifier element,
      InputType input_type = InputType::kDontCare);
  [[nodiscard]] StepBuilder SelectTab(
      ElementSpecifier tab_collection,
      size_t tab_index,
      InputType input_type = InputType::kDontCare,
      std::optional<size_t> expected_index_after_selection = std::nullopt);
  [[nodiscard]] StepBuilder SelectDropdownItem(
      ElementSpecifier collection,
      size_t item,
      InputType input_type = InputType::kDontCare);
  [[nodiscard]] StepBuilder EnterText(
      ElementSpecifier element,
      std::u16string text,
      TextEntryMode mode = TextEntryMode::kReplaceAll);
  [[nodiscard]] StepBuilder ActivateSurface(ElementSpecifier element);
  [[nodiscard]] StepBuilder FocusElement(ElementSpecifier element);
#if !BUILDFLAG(IS_IOS)
  [[nodiscard]] StepBuilder SendAccelerator(ElementSpecifier element,
                                            Accelerator accelerator);
  [[nodiscard]] StepBuilder SendKeyPress(ElementSpecifier element,
                                         KeyboardCode key,
                                         int flags = EF_NONE);
#endif
  [[nodiscard]] StepBuilder Confirm(ElementSpecifier element);

  // Logs the given arguments, in order, at level INFO.
  //
  // This is *roughly* (but not exactly) equivalent to:
  //   `Do([=](){ LOG(INFO) << args...; })`
  //
  // By default, values are captured at the time the Log step is created, rather
  // than when it is run. If you want the value to be captured at runtime, pass
  // `std::ref(value)` instead:
  //
  // ```
  //   int x = 0;
  //   RunTestSequence(
  //       /* maybe change the value of x */
  //       Log("Value of x at sequence creation: ", x),
  //       Log("Value of x right now: ", std::ref(x)));
  // ```
  template <typename... Args>
  [[nodiscard]] static StepBuilder Log(Args... args);

  // Dumps all of the elements in the current UI tree in all contexts.
  [[nodiscard]] StepBuilder DumpElements();

  // Dumps all of the elements in the current UI tree in the current context.
  [[nodiscard]] StepBuilder DumpElementsInContext();

  // Does an action at this point in the test sequence.
  template <typename A>
    requires internal::HasSignature<A, void()>
  [[nodiscard]] static StepBuilder Do(A&& action);

  // Performs a check and fails the test if `check_callback` returns false.
  template <typename C>
    requires internal::HasSignature<C, bool()>
  [[nodiscard]] static StepBuilder Check(
      C&& check_callback,
      std::string check_description = internal::kNoCheckDescriptionSpecified);

  // Calls `function` and applies `matcher` to the result. If the matcher does
  // not match, an appropriate error message is printed and the test fails.
  //
  // `matcher` should resolve or convert to a `Matcher<R>`.
  template <typename C, typename M, typename R = internal::ReturnTypeOf<C>>
    requires internal::HasSignature<C, R()>
  [[nodiscard]] static StepBuilder CheckResult(
      C&& function,
      M&& matcher,
      std::string check_description = internal::kNoCheckDescriptionSpecified);

  // Checks the value of `variable` against `matcher`. The variable can be any
  // local or class member that is guaranteed to still exist when the step is
  // executed; if its value at the time the step is executed does not match,
  // an appropriate error message is printed and the test fails.
  //
  // There is no need to wrap `variable` in e.g. `std::ref`; it is always
  // captured by reference.
  //
  // `matcher` should resolve or convert to a `Matcher<T>`.
  template <typename V, typename M, typename T = internal::MatcherTypeFor<V>>
  [[nodiscard]] static StepBuilder CheckVariable(
      V& variable,
      M&& matcher,
      std::string check_description = internal::kNoCheckDescriptionSpecified);

  // Checks that `check` returns true for element `element`. Will fail the test
  // sequence if `check` returns false - the callback should log any specific
  // error before returning.
  //
  // Note that unless you add .SetMustBeVisibleAtStart(true), this test step
  // will wait for `element` to be shown before proceeding.
  template <typename C>
    requires internal::HasSignature<C, bool(TrackedElement*)>
  [[nodiscard]] static StepBuilder CheckElement(ElementSpecifier element,
                                                C&& check);

  // As CheckElement(), but checks that the result of calling `function` on
  // `element` matches `matcher`. If not, the mismatch is printed and the test
  // fails.
  //
  // `matcher` should resolve or convert to a `Matcher<R>`.
  template <typename F, typename M, typename R = internal::ReturnTypeOf<F>>
    requires internal::HasSignature<F, R(TrackedElement*)>
  [[nodiscard]] static StepBuilder CheckElement(ElementSpecifier element,
                                                F&& function,
                                                M&& matcher);

  // Shorthand methods for working with basic ElementTracker events. The element
  // will have `step_callback` called on it. You may specify additional
  // constraints such as SetMustBeVisibleAtStart(), SetTransitionOnlyOnEvent(),
  // SetContext(), etc.
  //
  // `step_callback` arguments may be omitted from the left-hand side.
  template <typename T>
    requires internal::IsStepCallback<T>
  [[nodiscard]] static StepBuilder AfterShow(ElementSpecifier element,
                                             T&& step_callback);
  template <typename T>
    requires internal::IsStepCallback<T>
  [[nodiscard]] static StepBuilder AfterEvent(ElementSpecifier element,
                                              CustomElementEventType event_type,
                                              T&& step_callback);
  template <typename T>
    requires internal::HasCompatibleSignature<T, void(InteractionSequence*)>
  [[nodiscard]] static StepBuilder AfterHide(ElementSpecifier element,
                                             T&& step_callback);

  // Versions of the above that have no step callback; included for clarity and
  // brevity.
  [[nodiscard]] static StepBuilder WaitForShow(
      ElementSpecifier element,
      bool transition_only_on_event = false);
  [[nodiscard]] static StepBuilder WaitForHide(
      ElementSpecifier element,
      bool transition_only_on_event = false);
  [[nodiscard]] static StepBuilder WaitForEvent(ElementSpecifier element,
                                                CustomElementEventType event);

  // Equivalent to AfterShow() but the element must already be present.
  template <typename T>
    requires internal::IsStepCallback<T>
  [[nodiscard]] static StepBuilder WithElement(ElementSpecifier element,
                                               T&& step_callback);

  // Ensures that `element_to_check` is not currently present/visible.
  [[nodiscard]] static StepBuilder EnsureNotPresent(
      ElementIdentifier element_to_check);

  // Opposite of EnsureNotPresent. Equivalent to:
  // ```
  //   WithElement(element_to_check, base::DoNothing())
  // ```
  [[nodiscard]] static StepBuilder EnsurePresent(
      ElementSpecifier element_to_check);

  // Specifies an element not relative to any particular other element.
  using AbsoluteElementSpecifier = std::variant<
      // Specify an element that is known at the time the sequence is created.
      // The element must persist until the step executes.
      TrackedElement*,
      // Specify an element pointer that will be valid by the time the step
      // executes. Use `std::ref()` to wrap the pointer that will receive the
      // value.
      std::reference_wrapper<TrackedElement*>,
      // Find and return an element based on an arbitrary rule.
      base::OnceCallback<TrackedElement*()>,
      // Find and return an element in the given context based on a rule.
      base::OnceCallback<TrackedElement*(ElementContext)>>;

  // Names an element specified by `spec` as `name`. If `spec` requires a
  // context, the context of the current step will be used.
  //
  // For Views, prefer `InteractiveViewsTestApi::NameView()`.
  [[nodiscard]] StepBuilder NameElement(std::string_view name,
                                        AbsoluteElementSpecifier spec);

  // Calls `find_callback` to locate an element relative to element
  // `relative_to` and assign it `name`.
  //
  // For Views, prefer `InteractiveViewsTestApi::NameViewRelative()`.
  template <typename C>
    requires internal::HasSignature<C, TrackedElement*(TrackedElement*)>
  [[nodiscard]] StepBuilder NameElementRelative(ElementSpecifier relative_to,
                                                std::string_view name,
                                                C&& find_callback);

  // Adds an observed state with identifier `id` in the current context. Use
  // `WaitForState()` to wait for state changes. This is a useful way to wait
  // for an asynchronous state that isn't a UI element.
  //
  // To construct the observer on the fly as the test is running, use the
  // argument-forwarding version below.
  //
  // Note: Some types are unavailable; for any UTF-8 string type, use
  // std::string. For any UTF-16 type, use std::u16string.
  template <typename ObserverBase, typename Observer>
    requires(std::derived_from<Observer, ObserverBase> &&
             IsStateObserver<ObserverBase>)
  [[nodiscard]] StepBuilder ObserveState(
      StateIdentifier<ObserverBase> id,
      std::unique_ptr<Observer> state_observer);

  // Adds an observed state with identifier `id` in the current context. Use
  // `WaitForState()` to wait for state changes. This is a useful way to wait
  // for an asynchronous state that isn't a UI element.
  //
  // This version of the function forwards its arguments to the `Observer`'s
  // constructor, with some of them being evaluated at runtime:
  //  - Arguments wrapped in `std::ref()` will be unwrapped
  //  - Functions and callbacks will be evaluated and their return values used
  //
  // If you must pass a callback or function pointer to the observer's
  // constructor, use the other version of this method above.
  //
  // Note: Some types are unavailable; for any UTF-8 string type, use
  // std::string. For any UTF-16 type, use std::u16string.
  template <typename Observer, typename... Args>
    requires IsStateObserver<Observer>
  [[nodiscard]] StepBuilder ObserveState(StateIdentifier<Observer> id,
                                         Args&&... args);

  // Polls a state using a polling state observer with `id` and value callback
  // `callback`. See `PollingStateObserver` and
  // `DECLARE_POLLING_STATE_IDENTIFIER_VALUE()` for more info.
  //
  // Use WaitForState() to check the polled state. Note that unlike
  // `ObserveState()`, transient states may be missed, so prefer using a custom
  // event or `ObserveState()` when possible.
  template <typename T, typename C>
  [[nodiscard]] StepBuilder PollState(
      StateIdentifier<PollingStateObserver<T>> id,
      C&& callback,
      base::TimeDelta polling_interval =
          PollingStateObserver<T>::kDefaultPollingInterval);

  // Polls an element using a polling element with `element_identifier` in the
  // current context using state observer with `id` and value callback
  // `callback`. See `PollingElementStateObserver` and
  // `DECLARE_POLLING_ELEMENT_STATE_IDENTIFIER_VALUE()` for more info.
  //
  // Note that the actual value type is not T, but `std::optional<T>`, as the
  // state will have the value std::nullopt if the element is not present.
  //
  // Use WaitForState() to check the polled state. Note that unlike
  // `ObserveState()`, transient states may be missed, so prefer using a custom
  // event or `ObserveState()` when possible.
  template <typename T, typename C>
  [[nodiscard]] StepBuilder PollElement(
      StateIdentifier<PollingElementStateObserver<T>> id,
      ui::ElementIdentifier element_identifier,
      C&& callback,
      base::TimeDelta polling_interval =
          PollingStateObserver<T>::kDefaultPollingInterval);

  // Waits for the state of state observer `id` (bound with `ObserveState()` in
  // the current context) to match `value`. If `value` is a function, callback,
  // or `std::reference_wrapper`, it will be called or unwrapped as the step is
  // run, rather than having its value frozen when the test sequence is created.
  // A matcher may also be passed, and the step will proceed when the value of
  // the state satisfies the matcher.
  //
  // See /chrome/test/interaction/README.md for more information.
  template <typename O, typename V>
    requires IsStateObserver<O>
  [[nodiscard]] static MultiStep WaitForState(StateIdentifier<O> id, V&& value);

  // Checks that the current known state of observer `id` matches `value`. If
  // `value` is a function, callback, or `std::reference_wrapper`, it will be
  // called or unwrapped as the step is run, rather than having its value frozen
  // when the test sequence is created. A matcher may also be passed, and the
  // step will proceed when the value of the state satisfies the matcher.
  //
  // USE WITH CAUTION - if there's any chance of your state being transient or
  // asynchronous this will almost certainly cause your test to flake. Use only
  // to verify a state you have already observed through some other means.
  //
  // To this end, since they are inherently asynchronous, polling state
  // observers are not supported. To verify the state of a polling observer, you
  // must use `WaitForState()`.
  template <typename O, typename V>
    requires(IsStateObserver<O> && !IsPollingStateObserver<O>)
  [[nodiscard]] static StepBuilder CheckState(StateIdentifier<O> id, V&& value);

  // Ends observation of a state. Each `StateObserver` is normally cleaned up
  // at the end of a test. This cleans up the observer with `id` immediately,
  // including halting any associated polling.
  //
  // Typically unnecessary; included for completeness. Stopping an observation
  // might avoid a UAF, or allow the caller to re-use `id` for a different
  // observation in the same context.
  //
  // Must be called in the same context as `ObserveState()`, `PollState()`, etc.
  template <typename O>
    requires IsStateObserver<O>
  [[nodiscard]] StepBuilder StopObservingState(StateIdentifier<O> id);

  // Convenience method for waiting for a state to achieve a particular value.
  // Equivalent to:
  // ```
  //   PollState(id, callback, polling_interval),
  //   WaitForState(id, value),
  //   StopObservingState(id)
  // ```
  //
  // Note that you can use different identifiers in different subsequences of an
  // `InParallel` block, but not the same identifier.
  //
  // This is probably more than you need for a simple do-until loop; Use
  // PollUntil() instead where possible.
  template <typename T, typename C, typename M>
  [[nodiscard]] MultiStep PollStateUntil(
      StateIdentifier<PollingStateObserver<T>> id,
      C&& callback,
      M&& value,
      base::TimeDelta polling_interval =
          PollingStateObserver<T>::kDefaultPollingInterval);

  // Convenience version of PollStateUntil which polls until `callback` becomes
  // true; it uses a single internal identifier which means that unlike
  // `PollStateUntil()` with different `id`s, these cannot be used in parallel.
  template <typename C>
    requires internal::HasSignature<C, bool()>
  [[nodiscard]] MultiStep PollUntil(
      C&& callback,
      std::string description,
      base::TimeDelta polling_interval =
          PollingStateObserver<bool>::kDefaultPollingInterval);

  // Provides syntactic sugar so you can put "in any context" before an action
  // or test step rather than after. For example the following are equivalent:
  // ```
  //    PressButton(kElementIdentifier)
  //        .SetContext(InteractionSequence::ContextMode::kAny)
  //
  //    InAnyContext(PressButton(kElementIdentifier))
  // ```
  template <typename... Args>
    requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
  [[nodiscard]] static MultiStep InAnyContext(Args&&... args);

  // Provides syntactic sugar so you can put "inherit context from previous
  // step" around a step or steps to ensure a sequence executes in a specific
  // context. For example:
  // ```
  //    InAnyContext(WaitForShow(kMyElementInOtherContext)),
  //    InSameContext(
  //      PressButton(kMyElementInOtherContext),
  //      WaitForHide(kMyElementInOtherContext)
  //    ),
  // ```
  template <typename... Args>
    requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
  [[nodiscard]] static MultiStep InSameContext(Args&&... args);

  // Specifies that test step(s) should be executed in a specific context.
  template <typename... Args>
    requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
  [[nodiscard]] MultiStep InContext(ElementContext context, Args&&... args);

  // Specifies that test step(s) should be executed in the same context as a
  // specific `element`, which should be unique across contexts or a specific
  // named element.
  //
  // NOTE: If the previous step already references the element, prefer
  // `InSameContext()` as it has fewer limitations and handles elements that may
  // be present in multiple contexts.
  template <typename... Args>
    requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
  [[nodiscard]] static MultiStep InSameContextAs(ElementSpecifier element,
                                                 Args&&... steps);

  // Specifies that these test step(s) should be executed as soon as they are
  // eligible to trigger, one after the other. By default, once a step is
  // triggered, the system waits for a fresh call stack/message pump iteration
  // to run the step callback and/or check for the next step's triggering
  // condition.
  //
  // Use this when you want to respond to some event by doing a series of checks
  // immediately, e.g.:
  // ```
  //  PressButton(MyDialog::kCommitChangesButtonId),
  //  // Have to check the model when the dialog is completing because the model
  //  // goes away with the dialog.
  //  WithoutDelay(
  //    WaitForHide(MyDialog::kElementId),
  //    CheckResult(&CheckDialogModelCount, 3),
  //    CheckResult(&CheckDialogModelResult, MyDialogModel::Result::kUpdated)),
  // ```
  template <typename... Args>
    requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
  [[nodiscard]] static MultiStep WithoutDelay(Args&&... steps);

  // Creates a method named `Name` that takes the same arguments as `Steps()`
  // and returns a strongly-typed StepBlock called `<Name>Block` that can be
  // used as the input to specific verbs and control-flow structures.
#define DECLARE_STEP_BLOCK_FACTORY(Name)                        \
  class Name##Block : public internal::StepBlock<Name##Block> { \
   public:                                                      \
    using StepBlock::StepBlock;                                 \
  };                                                            \
  template <typename... Args>                                   \
    requires(internal::IsValueOrRvalue<Args> && ...)            \
  static Name##Block Name(Args&&... args) {                     \
    return Name##Block(Steps(std::forward<Args>(args)...));     \
  }

  // Use Then() to specify the step[s] to be executed when the condition of an
  // `If()` is true.
  DECLARE_STEP_BLOCK_FACTORY(Then)

  // Use Else() to specify the step[s] to be executed when the condition of an
  // `If()` is false.
  DECLARE_STEP_BLOCK_FACTORY(Else)

  // Executes `then_steps` if `condition` is true, else executes `else_steps`.
  template <typename C>
    requires internal::HasSignature<C, bool()>
  [[nodiscard]] static StepBuilder If(C&& condition,
                                      ThenBlock then_steps,
                                      ElseBlock else_steps = Else());

  // Executes `then_steps` if the result of `function` matches `matcher`, which
  // should resolve or convert to a `Matcher<R>`. Arguments to `function` may be
  // omitted.
  template <typename F, typename M, typename R = internal::ReturnTypeOf<F>>
    requires internal::HasCompatibleSignature<F, R(const InteractionSequence*)>
  [[nodiscard]] static StepBuilder IfMatches(F&& function,
                                             M&& matcher,
                                             ThenBlock then_steps,
                                             ElseBlock else_steps = Else());

  // As If*(), but the `condition` receives a pointer to `element`. If the
  // element is not present, null is passed instead (the step does not wait for
  // the element to become visible). Arguments to `condition` may be omitted
  // from the left.
  template <typename C>
    requires internal::IsCheckCallback<C, bool>
  [[nodiscard]] static StepBuilder IfElement(ElementSpecifier element,
                                             C&& condition,
                                             ThenBlock then_steps,
                                             ElseBlock else_steps = Else());

  // As IfElement(), but the result of `function` is compared against `matcher`.
  //
  // Arguments to `function` may be omitted from the left. `matcher` should
  // resolve or convert to a `Matcher<R>`.
  template <typename F, typename M, typename R = internal::ReturnTypeOf<F>>
    requires internal::IsCheckCallback<F, R>
  [[nodiscard]] static StepBuilder IfElementMatches(
      ElementSpecifier element,
      F&& function,
      M&& matcher,
      ThenBlock then_steps,
      ElseBlock else_steps = Else());

  // Use RunSubsequence() to specify each path of an `InParallel()` or
  // `AnyOf()`.
  DECLARE_STEP_BLOCK_FACTORY(RunSubsequence)

  // Executes two or more subsequences in parallel, independently of each other,
  // with the expectation that all will succeed. All subsequences must succeed
  // or the test will fail.
  //
  // This is useful when you are waiting for several discrete events, but the
  // order they may occur in is unspecified/undefined, and there is no way to
  // wait for them in sequence in a way that won't occasionally flake due to the
  // race condition.
  //
  // Side-effects due to callbacks during these subsequences should be
  // minimized, as one sequence could theoretically interfere with the
  // functioning of another.
  //
  // Syntax is:
  // ```
  // InParallel(
  //     RunSubsequence(...),
  //     RunSubsequence(...)
  //     [, RunSubsequence(...) ...] )
  // ```
  template <typename... Args>
    requires(sizeof...(Args) >= 2 &&
             (std::same_as<Args, RunSubsequenceBlock> && ...))
  [[nodiscard]] static StepBuilder InParallel(Args... subsequences);

  // Executes two or more subsequences in parallel, independently of each other,
  // with the expectation that at least one will succeed. (The others will be
  // canceled.) At least one of the sequences must succeed or the test will
  // fail.
  //
  // Side-effects due to callbacks during these subsequences should be
  // minimized, as one sequence could theoretically interfere with the
  // functioning of another, and no one sequence is guaranteed to execute to
  // completion.
  //
  // Syntax is:
  // ```
  // AnyOf(
  //     RunSubsequence(...),
  //     RunSubsequence(...)
  //     [, RunSubsequence(...) ...] )
  // ```
  template <typename... Args>
    requires(sizeof...(Args) >= 2 &&
             (std::same_as<Args, RunSubsequenceBlock> && ...))
  [[nodiscard]] static StepBuilder AnyOf(Args... additional);

  // Sets how to handle a case where a test attempts an operation that is not
  // supported in the current platform/build/environment. Default is to fail
  // the test. See chrome/test/interaction/README.md for best practices.
  //
  // Note that `reason` must always be specified, unless `action` is
  // `kFailTest`, in which case it may be empty.
  [[nodiscard]] StepBuilder SetOnIncompatibleAction(OnIncompatibleAction action,
                                                    const char* reason);

  // Used internally by methods in this class; do not call.
  internal::InteractiveTestPrivate& private_test_impl() {
    return *private_test_impl_;
  }

  // Adds a step or steps to the end of an existing MultiStep. Shorthand for
  // making one or more calls to `std::vector::emplace_back`.
  static void AddStep(MultiStep& dest, StepBuilder src);
  static void AddStep(MultiStep& dest, MultiStep src);

  // Equivalent to calling `AddDescriptionPrefix(prefix)` on every step in
  // `steps`.
  static void AddDescriptionPrefix(MultiStep& steps, std::string_view prefix);

  // Call this from any test verb which requires an environment suitable for
  // interactive testing. Typically, this means the test must be in an
  // environment where it can control mouse input, window activation, etc.
  //
  // Will crash a test which uses an inappropriate verb, with a description of
  // why the verb was disallowed.
  void RequireInteractiveTest();

 private:
  // Implementation for RunTestSequenceInContext().
  bool RunTestSequenceImpl(ElementContext context,
                           InteractionSequence::Builder builder);

  // Returns a callback to locate an element based on a pivot element and the
  // specification `spec`.
  using FindElementCallback =
      base::OnceCallback<TrackedElement*(TrackedElement*)>;
  static FindElementCallback GetFindElementCallback(
      AbsoluteElementSpecifier spec);

  // Helper method to add a step or steps to a sequence builder.
  static void AddStep(InteractionSequence::Builder& builder, MultiStep steps);
  template <typename T>
  static void AddStep(InteractionSequence::Builder& builder, T&& step);

  std::unique_ptr<internal::InteractiveTestPrivate> private_test_impl_;
};

// Template that adds InteractiveTestApi to any test fixture. No simulators are
// attached to test_util() so if you want to use verbs like PressButton() you
// will need to install your own simulator.
template <typename T>
class InteractiveTestMixin : public T, public InteractiveTestApi {
 public:
  template <typename... Args>
  explicit InteractiveTestMixin(Args&&... args)
      : T(std::forward<Args>(args)...), InteractiveTestApi() {}

  ~InteractiveTestMixin() override = default;

 protected:
  void SetUp() override {
    T::SetUp();
    private_test_impl().DoTestSetUp();
  }

  void TearDown() override {
    private_test_impl().DoTestTearDown();
    T::TearDown();
  }
};

// Template definitions:

// static
template <typename... Args>
  requires(internal::IsValueOrRvalue<Args> && ...)
InteractiveTestApi::MultiStep InteractiveTestApi::Steps(Args&&... args) {
  MultiStep result;
  (AddStep(result, std::forward<Args>(args)), ...);
  return result;
}

// static
template <typename T>
void InteractiveTestApi::AddStep(InteractionSequence::Builder& builder,
                                 T&& step) {
  builder.AddStep(std::forward<T>(step));
}

template <typename... Args>
  requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
bool InteractiveTestApi::RunTestSequenceInContext(ElementContext context,
                                                  Args&&... steps) {
  // TODO(dfried): is there any additional automation we need to do in order to
  // get proper error scoping, RunLoop timeout handling, etc.? We may have to
  // inject information directly into the steps or step callbacks; it's unclear.
  InteractionSequence::Builder builder;
  (AddStep(builder, std::forward<Args>(steps)), ...);
  return RunTestSequenceImpl(context, std::move(builder));
}

template <typename... Args>
  requires(sizeof...(Args) > 0 &&
           (ui::test::internal::IsValueOrRvalue<Args> && ...))
bool InteractiveTestApi::RunTestSequence(Args&&... steps) {
  const ElementContext context = private_test_impl_->default_context();
  CHECK(context)
      << "Default context must be set before test sequence can be run.";
  return RunTestSequenceInContext(context, std::forward<Args>(steps)...);
}

template <typename A>
  requires internal::HasSignature<A, void()>
// static
InteractiveTestApi::StepBuilder InteractiveTestApi::Do(A&& action) {
  StepBuilder builder;
  builder.SetDescription("Do()");
  builder.SetElementID(internal::kInteractiveTestPivotElementId);
  builder.SetStartCallback(
      base::OnceClosure(internal::MaybeBind(std::forward<A>(action))));
  return builder;
}

// static
template <typename T>
  requires internal::IsStepCallback<T>
InteractionSequence::StepBuilder InteractiveTestApi::AfterShow(
    ElementSpecifier element,
    T&& step_callback) {
  StepBuilder builder;
  builder.SetDescription("AfterShow()");
  builder.SetElement(element);
  builder.SetStartCallback(
      base::RectifyCallback<InteractionSequence::StepStartCallback>(
          internal::MaybeBind(std::forward<T>(step_callback))));
  return builder;
}

// static
template <typename T>
  requires internal::IsStepCallback<T>
InteractionSequence::StepBuilder InteractiveTestApi::AfterEvent(
    ElementSpecifier element,
    CustomElementEventType event_type,
    T&& step_callback) {
  StepBuilder builder;
  builder.SetDescription(
      base::StrCat({"AfterEvent( ", event_type.GetName(), " )"}));
  builder.SetElement(element);
  builder.SetType(InteractionSequence::StepType::kCustomEvent, event_type);
  builder.SetStartCallback(
      base::RectifyCallback<InteractionSequence::StepStartCallback>(
          internal::MaybeBind(std::forward<T>(step_callback))));
  return builder;
}

// static
template <typename T>
  requires internal::HasCompatibleSignature<T, void(InteractionSequence*)>
InteractionSequence::StepBuilder InteractiveTestApi::AfterHide(
    ElementSpecifier element,
    T&& step_callback) {
  StepBuilder builder;
  builder.SetDescription("AfterHide()");
  builder.SetElement(element);
  builder.SetType(InteractionSequence::StepType::kHidden);
  using Callback = base::OnceCallback<void(InteractionSequence*)>;
  builder.SetStartCallback(
      base::BindOnce([](Callback callback, InteractionSequence* seq,
                        TrackedElement*) { std::move(callback).Run(seq); },
                     base::RectifyCallback<Callback>(
                         internal::MaybeBind(std::forward<T>(step_callback)))));
  return builder;
}

// static
template <typename T>
  requires internal::IsStepCallback<T>
InteractionSequence::StepBuilder InteractiveTestApi::WithElement(
    ElementSpecifier element,
    T&& step_callback) {
  StepBuilder builder;
  builder.SetDescription("WithElement()");
  builder.SetElement(element);
  builder.SetStartCallback(
      base::RectifyCallback<InteractionSequence::StepStartCallback>(
          internal::MaybeBind(std::forward<T>(step_callback))));
  builder.SetMustBeVisibleAtStart(true);
  return builder;
}

// static
template <typename C>
  requires internal::HasSignature<C, TrackedElement*(TrackedElement*)>
InteractionSequence::StepBuilder InteractiveTestApi::NameElementRelative(
    ElementSpecifier relative_to,
    std::string_view name,
    C&& find_callback) {
  StepBuilder builder;
  builder.SetDescription(
      base::StringPrintf("NameElementRelative( \"%s\" )", name.data()));
  builder.SetElement(relative_to);
  builder.SetMustBeVisibleAtStart(true);
  builder.SetStartCallback(base::BindOnce(
      [](base::OnceCallback<TrackedElement*(TrackedElement*)> find_callback,
         std::string name, ui::InteractionSequence* seq,
         ui::TrackedElement* el) {
        TrackedElement* const result = std::move(find_callback).Run(el);
        if (!result) {
          LOG(ERROR) << "NameElement(): No View found.";
          seq->FailForTesting();
          return;
        }
        seq->NameElement(result, name);
      },
      ui::test::internal::MaybeBind(std::forward<C>(find_callback)),
      std::string(name)));
  return builder;
}

// static
template <typename... Args>
  requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
InteractiveTestApi::MultiStep InteractiveTestApi::InAnyContext(Args&&... args) {
  auto steps = Steps(std::forward<Args>(args)...);
  for (auto& step : steps) {
    step.SetContext(InteractionSequence::ContextMode::kAny)
        .AddDescriptionPrefix("InAnyContext()");
  }
  return steps;
}

// static
template <typename... Args>
  requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
InteractiveTestApi::MultiStep InteractiveTestApi::InSameContext(
    Args&&... args) {
  auto steps = Steps(std::forward<Args>(args)...);
  for (auto& step : steps) {
    step.SetContext(InteractionSequence::ContextMode::kFromPreviousStep)
        .AddDescriptionPrefix("InSameContext()");
  }
  return steps;
}

template <typename... Args>
  requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
InteractiveTestApi::MultiStep InteractiveTestApi::InContext(
    ElementContext context,
    Args&&... args) {
  // This context may not yet exist, but we want the pivot element to exist.
  private_test_impl_->MaybeAddPivotElement(context);
  auto steps = Steps(std::forward<Args>(args)...);
  const std::string caller =
      base::StringPrintf("InContext( %p, )", static_cast<const void*>(context));
  for (auto& step : steps) {
    step.SetContext(context).AddDescriptionPrefix(caller);
  }
  return steps;
}

// static
template <typename... Args>
  requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
InteractiveTestApi::MultiStep InteractiveTestApi::InSameContextAs(
    ElementSpecifier element,
    Args&&... steps) {
  return Steps(
      WithElement(element, base::DoNothing())
          .SetContext(InteractionSequence::ContextMode::kAny)
          .SetDescription("InSameContextAs() - locate reference element"),
      InSameContext(std::forward<Args>(steps)...));
}

// static
template <typename... Args>
  requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
InteractiveTestApi::MultiStep InteractiveTestApi::WithoutDelay(
    Args&&... steps) {
  auto result = Steps(std::forward<Args>(steps)...);
  for (auto& step : result) {
    step.SetStepStartMode(InteractionSequence::StepStartMode::kImmediate)
        .AddDescriptionPrefix("WithoutDelay()");
  }
  return result;
}

// static
template <typename C>
  requires internal::IsCheckCallback<C, bool>
InteractionSequence::StepBuilder InteractiveTestApi::IfElement(
    ElementSpecifier element,
    C&& condition,
    ThenBlock then_steps,
    ElseBlock else_steps) {
  auto step = IfElementMatches(element, std::forward<C>(condition), true,
                               std::move(then_steps), std::move(else_steps));
  step.SetDescription("IfElement()");
  return step;
}

// static
template <typename F, typename M, typename R>
  requires internal::IsCheckCallback<F, R>
InteractionSequence::StepBuilder InteractiveTestApi::IfElementMatches(
    ElementSpecifier element,
    F&& function,
    M&& matcher,
    ThenBlock then_steps,
    ElseBlock else_steps) {
  InteractionSequence::StepBuilder step;
  step.SetElement(element);
  step.SetSubsequenceMode(InteractionSequence::SubsequenceMode::kAtMostOne);
  using FunctionType =
      base::OnceCallback<R(const InteractionSequence*, const TrackedElement*)>;
  using MatcherType = internal::MatcherTypeFor<R>;
  step.AddSubsequence(
      internal::BuildSubsequence(std::move(then_steps.steps())),
      base::BindOnce(
          [](FunctionType function, testing::Matcher<MatcherType> matcher,
             const InteractionSequence* seq, const TrackedElement* el) -> bool {
            return matcher.Matches(
                MatcherType(std::move(function).Run(seq, el)));
          },
          base::RectifyCallback<FunctionType>(
              internal::MaybeBind(std::forward<F>(function))),
          testing::Matcher<MatcherType>(std::forward<M>(matcher))));
  if (!else_steps.steps().empty()) {
    step.AddSubsequence(
        internal::BuildSubsequence(std::move(else_steps.steps())));
  }
  step.SetDescription("IfElementMatches()");
  return step;
}

// static
template <typename C>
  requires internal::HasSignature<C, bool()>
InteractionSequence::StepBuilder InteractiveTestApi::If(C&& condition,
                                                        ThenBlock then_steps,
                                                        ElseBlock else_steps) {
  auto step = IfMatches(std::forward<C>(condition), true, std::move(then_steps),
                        std::move(else_steps));
  step.SetDescription("If()");
  return step;
}

// static
template <typename F, typename M, typename R>
  requires internal::HasCompatibleSignature<F, R(const InteractionSequence*)>
InteractionSequence::StepBuilder InteractiveTestApi::IfMatches(
    F&& function,
    M&& matcher,
    ThenBlock then_steps,
    ElseBlock else_steps) {
  auto step = IfElementMatches(
      internal::kInteractiveTestPivotElementId,
      base::BindOnce(
          [](base::OnceCallback<R(const InteractionSequence*)> function,
             const InteractionSequence* seq, const ui::TrackedElement*) {
            return std::move(function).Run(seq);
          },
          base::RectifyCallback<R(const InteractionSequence*)>(
              internal::MaybeBind(std::forward<F>(function)))),
      std::forward<M>(matcher), std::move(then_steps), std::move(else_steps));
  step.SetDescription("IfMatches()");
  return step;
}

// static
template <typename... Args>
  requires(sizeof...(Args) >= 2 &&
           (std::same_as<Args, InteractiveTestApi::RunSubsequenceBlock> && ...))
InteractionSequence::StepBuilder InteractiveTestApi::InParallel(
    Args... subsequences) {
  InteractionSequence::StepBuilder step;
  step.SetElementID(internal::kInteractiveTestPivotElementId);
  step.SetSubsequenceMode(InteractionSequence::SubsequenceMode::kAll);
  (step.AddSubsequence(
       internal::BuildSubsequence(std::move(subsequences.steps()))),
   ...);
  step.SetDescription("InParallel()");
  return step;
}

// static
template <typename... Args>
  requires(sizeof...(Args) >= 2 &&
           (std::same_as<Args, InteractiveTestApi::RunSubsequenceBlock> && ...))
InteractionSequence::StepBuilder InteractiveTestApi::AnyOf(
    Args... subsequences) {
  InteractionSequence::StepBuilder step;
  step.SetElementID(internal::kInteractiveTestPivotElementId);
  step.SetSubsequenceMode(InteractionSequence::SubsequenceMode::kAtLeastOne);
  (step.AddSubsequence(
       internal::BuildSubsequence(std::move(subsequences.steps()))),
   ...);
  step.SetDescription("AnyOf()");
  return step;
}

template <typename ObserverBase, typename Observer>
  requires(std::derived_from<Observer, ObserverBase> &&
           IsStateObserver<ObserverBase>)
InteractionSequence::StepBuilder InteractiveTestApi::ObserveState(
    StateIdentifier<ObserverBase> id,
    std::unique_ptr<Observer> observer) {
  auto step = CheckElement(
      internal::kInteractiveTestPivotElementId,
      base::BindOnce(
          [](InteractiveTestApi* api, ElementIdentifier id,
             std::unique_ptr<Observer> observer, TrackedElement* el) {
            return api->private_test_impl().AddStateObserver(
                id, el->context(), std::move(observer));
          },
          base::Unretained(this), id.identifier(), std::move(observer)));
  step.SetDescription("ObserveState()");
  return step;
}

template <typename Observer, typename... Args>
  requires IsStateObserver<Observer>
InteractionSequence::StepBuilder InteractiveTestApi::ObserveState(
    StateIdentifier<Observer> id,
    Args&&... args) {
  auto step = CheckElement(
      internal::kInteractiveTestPivotElementId,
      base::BindOnce(
          [](InteractiveTestApi* api, ElementIdentifier id,
             std::remove_cvref_t<Args>... args, TrackedElement* el) {
            return api->private_test_impl().AddStateObserver(
                id, el->context(),
                std::make_unique<Observer>(
                    internal::UnwrapArgument<Args>(std::move(args))...));
          },
          base::Unretained(this), id.identifier(), std::move(args)...));
  step.SetDescription("ObserveState()");
  return step;
}

template <typename T, typename C>
InteractionSequence::StepBuilder InteractiveTestApi::PollState(
    StateIdentifier<PollingStateObserver<T>> id,
    C&& callback,
    base::TimeDelta polling_interval) {
#if defined(__clang__) && (__clang_major__ < 17)
  using Cb = typename PollingStateObserver<T>::PollCallback;
#else
  using Cb = PollingStateObserver<T>::PollCallback;
#endif
  auto step = CheckElement(
      internal::kInteractiveTestPivotElementId,
      base::BindOnce(
          [](InteractiveTestApi* api, ElementIdentifier id, Cb callback,
             base::TimeDelta polling_interval, TrackedElement* el) {
            return api->private_test_impl().AddStateObserver(
                id, el->context(),
                std::make_unique<PollingStateObserver<T>>(std::move(callback),
                                                          polling_interval));
          },
          base::Unretained(this), id.identifier(),
          internal::MaybeBindRepeating(std::forward<C>(callback)),
          polling_interval));
  step.SetDescription("PollState()");
  return step;
}

template <typename T, typename C>
InteractionSequence::StepBuilder InteractiveTestApi::PollElement(
    StateIdentifier<PollingElementStateObserver<T>> id,
    ui::ElementIdentifier element_identifier,
    C&& callback,
    base::TimeDelta polling_interval) {
#if defined(__clang__) && (__clang_major__ < 17)
  using Cb = typename PollingElementStateObserver<T>::PollElementCallback;
#else
  using Cb = PollingElementStateObserver<T>::PollElementCallback;
#endif
  auto step = WithElement(
      internal::kInteractiveTestPivotElementId,
      base::BindOnce(
          [](InteractiveTestApi* api, ElementIdentifier id,
             ElementIdentifier element_id, Cb callback,
             base::TimeDelta polling_interval, InteractionSequence* seq,
             TrackedElement* el) {
            if (!api->private_test_impl().AddStateObserver(
                    id, el->context(),
                    std::make_unique<PollingElementStateObserver<T>>(
                        element_id,
                        seq->IsCurrentStepInAnyContextForTesting()
                            ? std::nullopt
                            : std::make_optional(el->context()),
                        std::move(callback), polling_interval))) {
              seq->FailForTesting();
            }
          },
          base::Unretained(this), id.identifier(), element_identifier,
          internal::MaybeBindRepeating(std::forward<C>(callback)),
          polling_interval));
  step.SetDescription(base::StringPrintf("PollElementState(%s)",
                                         element_identifier.GetName().c_str()));
  return step;
}

// static
template <typename O, typename V>
  requires IsStateObserver<O>
InteractiveTestApi::MultiStep InteractiveTestApi::WaitForState(
    StateIdentifier<O> id,
    V&& value) {
  using T = typename O::ValueType;
  using U = internal::MatcherTypeFor<V>;
  auto wait_callback = base::BindOnce(
      [](ElementIdentifier id, U value, InteractionSequence* seq,
         TrackedElement* el) {
        auto* const typed = internal::StateObserverElementT<T>::LookupElement(
            id, el->context(), seq->IsCurrentStepInAnyContextForTesting());
        if (!typed) {
          LOG(ERROR) << "No state observer registered for identifier " << id
                     << " in the current context. You must observe a state in "
                        "the same context you observed it in.";
          seq->FailForTesting();
          return;
        }
        typed->SetTarget(internal::CreateMatcherFromValue<T>(value));
      },
      id.identifier(), U(std::forward<V>(value)));
  auto result = Steps(WithElement(internal::kInteractiveTestPivotElementId,
                                  std::move(wait_callback)),
                      WaitForShow(id.identifier()));
  AddDescriptionPrefix(result, "WaitForState()");
  return result;
}

// static
template <typename O, typename V>
  requires(IsStateObserver<O> && !IsPollingStateObserver<O>)
InteractiveTestApi::StepBuilder InteractiveTestApi::CheckState(
    StateIdentifier<O> id,
    V&& value) {
  using T = typename O::ValueType;
  using U = internal::MatcherTypeFor<V>;
  auto check_callback = base::BindOnce(
      [](ElementIdentifier id, U value, InteractionSequence* seq,
         TrackedElement* el) {
        auto* const typed = internal::StateObserverElementT<T>::LookupElement(
            id, el->context(), seq->IsCurrentStepInAnyContextForTesting());
        if (!typed) {
          LOG(ERROR) << "No state observer registered for identifier " << id
                     << " in the current context. You must observe a state in "
                        "the same context you observed it in.";
          seq->FailForTesting();
          return;
        }
        if (!internal::MatchAndExplain(
                "CheckState()", internal::CreateMatcherFromValue<T>(value),
                typed->current_value())) {
          seq->FailForTesting();
          return;
        }
      },
      id.identifier(), U(std::forward<V>(value)));

  auto step = WithElement(internal::kInteractiveTestPivotElementId,
                          std::move(check_callback));
  step.SetDescription(
      base::StringPrintf("CheckState(%s)", id.identifier().GetName()));
  return step;
}

template <typename O>
  requires IsStateObserver<O>
InteractiveTestApi::StepBuilder InteractiveTestApi::StopObservingState(
    StateIdentifier<O> id) {
  auto step = WithElement(
      internal::kInteractiveTestPivotElementId,
      base::BindOnce(
          [](InteractiveTestApi* api, ElementIdentifier id,
             InteractionSequence* seq, TrackedElement* el) {
            const auto context = seq->IsCurrentStepInAnyContextForTesting()
                                     ? ElementContext()
                                     : el->context();
            if (!api->private_test_impl().RemoveStateObserver(id, context)) {
              seq->FailForTesting();
            }
          },
          base::Unretained(this), id.identifier()));
  step.SetDescription(base::StringPrintf("StopObservingState(%s)",
                                         id.identifier().GetName().c_str()));
  return step;
}

template <typename T, typename C, typename M>
InteractiveTestApi::MultiStep InteractiveTestApi::PollStateUntil(
    StateIdentifier<PollingStateObserver<T>> id,
    C&& callback,
    M&& value,
    base::TimeDelta polling_interval) {
  auto steps =
      Steps(PollState(id, std::forward<C>(callback), polling_interval),
            WaitForState(id, std::forward<M>(value)), StopObservingState(id));
  AddDescriptionPrefix(steps, "PollStateUntil()");
  return steps;
}

template <typename C>
  requires internal::HasSignature<C, bool()>
InteractiveTestApi::MultiStep InteractiveTestApi::PollUntil(
    C&& callback,
    std::string description,
    base::TimeDelta polling_interval) {
  auto steps =
      PollStateUntil(internal::kInteractiveTestPollUntilState,
                     std::forward<C>(callback), true, polling_interval);
  AddDescriptionPrefix(steps, description);
  return steps;
}

// static
template <typename... Args>
InteractiveTestApi::StepBuilder InteractiveTestApi::Log(Args... args) {
  auto step = Do(base::BindOnce(
      [](std::remove_cvref_t<Args>... args) {
        auto info = COMPACT_GOOGLE_LOG_INFO;
        ((info.stream() << internal::UnwrapArgument<Args>(std::move(args))),
         ...);
      },
      std::move(args)...));
  step.SetDescription("Log()");
  return step;
}

// static
template <typename C>
  requires internal::HasSignature<C, bool()>
InteractiveTestApi::StepBuilder InteractiveTestApi::Check(
    C&& check_callback,
    std::string check_description) {
  StepBuilder builder;
  builder.SetDescription(
      base::StringPrintf("Check(\"%s\")", check_description.c_str()));
  builder.SetElementID(internal::kInteractiveTestPivotElementId);
  builder.SetStartCallback(base::BindOnce(
      [](base::OnceCallback<bool()> check_callback, InteractionSequence* seq,
         TrackedElement*) {
        const bool result = std::move(check_callback).Run();
        if (!result) {
          seq->FailForTesting();
        }
      },
      internal::MaybeBind(std::forward<C>(check_callback))));
  return builder;
}

// static
template <typename C, typename M, typename R>
  requires internal::HasSignature<C, R()>
InteractionSequence::StepBuilder InteractiveTestApi::CheckResult(
    C&& function,
    M&& matcher,
    std::string check_description) {
  using MatcherType = internal::MatcherTypeFor<R>;
  return std::move(
      Check(base::BindOnce(
                [](base::OnceCallback<R()> function,
                   testing::Matcher<MatcherType> matcher) {
                  return internal::MatchAndExplain(
                      "CheckResult()", matcher,
                      MatcherType(std::move(function).Run()));
                },
                internal::MaybeBind(std::forward<C>(function)),
                testing::Matcher<MatcherType>(std::forward<M>(matcher))))
          .SetDescription(base::StringPrintf("CheckResult(\"%s\")",
                                             check_description.c_str())));
}

// static
template <typename V, typename M, typename T>
InteractionSequence::StepBuilder InteractiveTestApi::CheckVariable(
    V& variable,
    M&& matcher,
    std::string check_description) {
  return std::move(
      Check(base::BindOnce(
                [](std::reference_wrapper<V> ref, testing::Matcher<T> matcher) {
                  return internal::MatchAndExplain("CheckVariable()", matcher,
                                                   ref.get());
                },
                std::ref(variable),
                testing::Matcher<T>(std::forward<M>(matcher))))
          .SetDescription(base::StringPrintf("CheckVariable(\"%s\")",
                                             check_description.c_str())));
}

// static
template <typename C>
  requires internal::HasSignature<C, bool(TrackedElement*)>
InteractionSequence::StepBuilder InteractiveTestApi::CheckElement(
    ElementSpecifier element,
    C&& check) {
  return CheckElement(element, std::forward<C>(check), true);
}

// static
template <typename F, typename M, typename R>
  requires internal::HasSignature<F, R(TrackedElement*)>
InteractionSequence::StepBuilder InteractiveTestApi::CheckElement(
    ElementSpecifier element,
    F&& function,
    M&& matcher) {
  StepBuilder builder;
  builder.SetDescription("CheckElement()");
  builder.SetElement(element);
  using MatcherType = internal::MatcherTypeFor<R>;
  builder.SetStartCallback(base::BindOnce(
      [](base::OnceCallback<R(TrackedElement*)> function,
         testing::Matcher<MatcherType> matcher, InteractionSequence* seq,
         TrackedElement* el) {
        if (!internal::MatchAndExplain(
                "CheckElement()", matcher,
                MatcherType(std::move(function).Run(el)))) {
          seq->FailForTesting();
        }
      },
      internal::MaybeBind(std::forward<F>(function)),
      testing::Matcher<MatcherType>(std::forward<M>(matcher))));
  return builder;
}

}  // namespace ui::test

#endif  // UI_BASE_INTERACTION_INTERACTIVE_TEST_H_