#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 {
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;
template <typename... Args>
requires(internal::IsValueOrRvalue<Args> && ...)
static MultiStep Steps(Args&&... args);
InteractionTestUtil& test_util() { return private_test_impl_->test_util(); }
template <typename... Args>
requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
bool RunTestSequenceInContext(ElementContext context, Args&&... steps);
template <typename... Args>
requires(sizeof...(Args) > 0 &&
(ui::test::internal::IsValueOrRvalue<Args> && ...))
bool RunTestSequence(Args&&... steps);
[[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);
template <typename... Args>
[[nodiscard]] static StepBuilder Log(Args... args);
[[nodiscard]] StepBuilder DumpElements();
[[nodiscard]] StepBuilder DumpElementsInContext();
template <typename A>
requires internal::HasSignature<A, void()>
[[nodiscard]] static StepBuilder Do(A&& action);
template <typename C>
requires internal::HasSignature<C, bool()>
[[nodiscard]] static StepBuilder Check(
C&& check_callback,
std::string check_description = internal::kNoCheckDescriptionSpecified);
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);
template <typename V, typename M, typename T = internal::MatcherTypeFor<V>>
[[nodiscard]] static StepBuilder CheckVariable(
V& variable,
M&& matcher,
std::string check_description = internal::kNoCheckDescriptionSpecified);
template <typename C>
requires internal::HasSignature<C, bool(TrackedElement*)>
[[nodiscard]] static StepBuilder CheckElement(ElementSpecifier element,
C&& check);
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);
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);
[[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);
template <typename T>
requires internal::IsStepCallback<T>
[[nodiscard]] static StepBuilder WithElement(ElementSpecifier element,
T&& step_callback);
[[nodiscard]] static StepBuilder EnsureNotPresent(
ElementIdentifier element_to_check);
[[nodiscard]] static StepBuilder EnsurePresent(
ElementSpecifier element_to_check);
using AbsoluteElementSpecifier = std::variant<
TrackedElement*,
std::reference_wrapper<TrackedElement*>,
base::OnceCallback<TrackedElement*()>,
base::OnceCallback<TrackedElement*(ElementContext)>>;
[[nodiscard]] StepBuilder NameElement(std::string_view name,
AbsoluteElementSpecifier spec);
template <typename C>
requires internal::HasSignature<C, TrackedElement*(TrackedElement*)>
[[nodiscard]] StepBuilder NameElementRelative(ElementSpecifier relative_to,
std::string_view name,
C&& find_callback);
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);
template <typename Observer, typename... Args>
requires IsStateObserver<Observer>
[[nodiscard]] StepBuilder ObserveState(StateIdentifier<Observer> id,
Args&&... args);
template <typename T, typename C>
[[nodiscard]] StepBuilder PollState(
StateIdentifier<PollingStateObserver<T>> id,
C&& callback,
base::TimeDelta polling_interval =
PollingStateObserver<T>::kDefaultPollingInterval);
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);
template <typename O, typename V>
requires IsStateObserver<O>
[[nodiscard]] static MultiStep WaitForState(StateIdentifier<O> id, V&& value);
template <typename O, typename V>
requires(IsStateObserver<O> && !IsPollingStateObserver<O>)
[[nodiscard]] static StepBuilder CheckState(StateIdentifier<O> id, V&& value);
template <typename O>
requires IsStateObserver<O>
[[nodiscard]] StepBuilder StopObservingState(StateIdentifier<O> id);
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);
template <typename C>
requires internal::HasSignature<C, bool()>
[[nodiscard]] MultiStep PollUntil(
C&& callback,
std::string description,
base::TimeDelta polling_interval =
PollingStateObserver<bool>::kDefaultPollingInterval);
template <typename... Args>
requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
[[nodiscard]] static MultiStep InAnyContext(Args&&... args);
template <typename... Args>
requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
[[nodiscard]] static MultiStep InSameContext(Args&&... args);
template <typename... Args>
requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
[[nodiscard]] MultiStep InContext(ElementContext context, Args&&... args);
template <typename... Args>
requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
[[nodiscard]] static MultiStep InSameContextAs(ElementSpecifier element,
Args&&... steps);
template <typename... Args>
requires(sizeof...(Args) > 0 && (internal::IsValueOrRvalue<Args> && ...))
[[nodiscard]] static MultiStep WithoutDelay(Args&&... steps);
#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)...)); \
}
DECLARE_STEP_BLOCK_FACTORY(Then)
DECLARE_STEP_BLOCK_FACTORY(Else)
template <typename C>
requires internal::HasSignature<C, bool()>
[[nodiscard]] static StepBuilder If(C&& condition,
ThenBlock then_steps,
ElseBlock else_steps = Else());
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());
template <typename C>
requires internal::IsCheckCallback<C, bool>
[[nodiscard]] static StepBuilder IfElement(ElementSpecifier element,
C&& condition,
ThenBlock then_steps,
ElseBlock else_steps = Else());
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());
DECLARE_STEP_BLOCK_FACTORY(RunSubsequence)
template <typename... Args>
requires(sizeof...(Args) >= 2 &&
(std::same_as<Args, RunSubsequenceBlock> && ...))
[[nodiscard]] static StepBuilder InParallel(Args... subsequences);
template <typename... Args>
requires(sizeof...(Args) >= 2 &&
(std::same_as<Args, RunSubsequenceBlock> && ...))
[[nodiscard]] static StepBuilder AnyOf(Args... additional);
[[nodiscard]] StepBuilder SetOnIncompatibleAction(OnIncompatibleAction action,
const char* reason);
internal::InteractiveTestPrivate& private_test_impl() {
return *private_test_impl_;
}
static void AddStep(MultiStep& dest, StepBuilder src);
static void AddStep(MultiStep& dest, MultiStep src);
static void AddDescriptionPrefix(MultiStep& steps, std::string_view prefix);
void RequireInteractiveTest();
private:
bool RunTestSequenceImpl(ElementContext context,
InteractionSequence::Builder builder);
using FindElementCallback =
base::OnceCallback<TrackedElement*(TrackedElement*)>;
static FindElementCallback GetFindElementCallback(
AbsoluteElementSpecifier spec);
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 <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 <typename... Args>
requires(internal::IsValueOrRvalue<Args> && ...)
InteractiveTestApi::MultiStep InteractiveTestApi::Steps(Args&&... args) {
MultiStep result;
(AddStep(result, std::forward<Args>(args)), ...);
return result;
}
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) {
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()>
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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) {
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;
}
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)...));
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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())));
}
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())));
}
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);
}
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;
}
}
#endif