blob: 57154e76cc5a6f54af2f3e6a0f4223401610a57e [file] [log] [blame]
// 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 <type_traits>
#include <utility>
#include <vector>
#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 "third_party/abseil-cpp/absl/types/variant.h"
#include "ui/base/interaction/element_identifier.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_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 `InteractiveTestT<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] or
// InteractiveBrowserTest[Api], which inherit from this class.
class InteractiveTestApi {
public:
explicit InteractiveTestApi(
std::unique_ptr<internal::InteractiveTestPrivate> private_test_impl);
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;
// Construct a MultiStep from one or more StepBuilders and/or MultiSteps.
template <typename... 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>
bool RunTestSequenceInContext(ElementContext context, Args&&... steps);
// An ElementSpecifier holds either an ElementIdentifier or a
// base::StringPiece denoting a named element in the test sequence.
using ElementSpecifier = internal::ElementSpecifier;
// 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.
//
// TODO(dfried): in the future, these will be supplanted/supplemented by more
// flexible primitives that allow multiple actions in the same step in the
// future.
[[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);
[[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);
#if !BUILDFLAG(IS_IOS)
[[nodiscard]] StepBuilder SendAccelerator(ElementSpecifier element,
Accelerator accelerator);
#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);
// 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 AfterActivate(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::IsStepCallback<T>
[[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 WaitForActivate(ElementSpecifier element);
[[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);
// Adds steps to the sequence that ensure that `element_to_check` is not
// present. Flushes the current message queue to ensure that if e.g. the
// previous step was responding to elements being added, the
// `element_to_check` may not have had its shown event called yet.
[[nodiscard]] static MultiStep EnsureNotPresent(
ElementIdentifier element_to_check);
// Opposite of EnsureNotPresent. Flushes the current message queue and then
// checks that the specified element is [still] present. Equivalent to:
// ```
// FlushEvents(),
// WithElement(element_to_check, base::DoNothing())
// ```
[[nodiscard]] static MultiStep EnsurePresent(
ElementSpecifier element_to_check);
// Specifies an element not relative to any particular other element.
using AbsoluteElementSpecifier = absl::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 `InteractiveViewsTest::NameView()`.
[[nodiscard]] StepBuilder NameElement(base::StringPiece name,
AbsoluteElementSpecifier spec);
// Calls `find_callback` to locate an element relative to element
// `relative_to` and assign it `name`.
//
// For Views, prefer `InteractiveViewsTest::NameViewRelative()`.
template <typename C>
requires internal::HasSignature<C, TrackedElement*(TrackedElement*)>
[[nodiscard]] StepBuilder NameElementRelative(ElementSpecifier relative_to,
base::StringPiece name,
C&& find_callback);
// Ensures that the next step does not piggyback on the previous step(s), but
// rather, executes on a fresh message loop. Normally, steps will continue to
// trigger on the same call stack until a start condition is not met.
//
// Use sparingly, and only when e.g. re-entrancy issues prevent the test from
// otherwise working properly.
[[nodiscard]] static MultiStep FlushEvents();
// 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> &&
internal::IsValidMatcherType<typename Observer::ValueType>
[[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 internal::IsValidMatcherType<typename Observer::ValueType>
[[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>
requires internal::IsValidMatcherType<T>
[[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>
requires internal::IsValidMatcherType<T>
[[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>
[[nodiscard]] static MultiStep WaitForState(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>
[[nodiscard]] StepBuilder StopObservingState(StateIdentifier<O> id);
// 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))
// ```
//
// TODO(dfried): consider if we should have a version that takes variadic
// arguments and applies "in any context" to all of them?
[[nodiscard]] static MultiStep InAnyContext(MultiStep steps);
template <typename T>
[[nodiscard]] static StepBuilder InAnyContext(T&& step);
// 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(Steps(
// PressButton(kMyElementInOtherContext),
// WaitForHide(kMyElementInOtherContext)
// )),
// ```
[[nodiscard]] static MultiStep InSameContext(MultiStep steps);
template <typename T>
[[nodiscard]] static StepBuilder InSameContext(T&& step);
[[nodiscard]] MultiStep InContext(ElementContext context, MultiStep steps);
template <typename T>
[[nodiscard]] StepBuilder InContext(ElementContext context, T&& step);
// Executes `then_steps` if `condition` is true, else executes `else_steps`.
template <typename C, typename T, typename E = MultiStep>
requires internal::HasSignature<C, bool()>
[[nodiscard]] static StepBuilder If(C&& condition,
T&& then_steps,
E&& else_steps = MultiStep());
// 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 T,
typename E = MultiStep,
typename R = internal::ReturnTypeOf<F>>
requires internal::HasCompatibleSignature<F, R(const InteractionSequence*)>
[[nodiscard]] static StepBuilder IfMatches(F&& function,
M&& matcher,
T&& then_steps,
E&& else_steps = MultiStep());
// 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, typename T, typename E = MultiStep>
requires internal::IsCheckCallback<C, bool>
[[nodiscard]] static StepBuilder IfElement(ElementSpecifier element,
C&& condition,
T&& then_steps,
E&& else_steps = MultiStep());
// 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 T,
typename E = MultiStep,
typename R = internal::ReturnTypeOf<F>>
requires internal::IsCheckCallback<F, R>
[[nodiscard]] static StepBuilder IfElementMatches(
ElementSpecifier element,
F&& function,
M&& matcher,
T&& then_steps,
E&& else_steps = MultiStep());
// Executes each of `sequences` in parallel, independently of each other, with
// the expectation that all will succeed. Each sequence should be a step or
// MultiStep.
//
// All of `sequences` 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.
template <typename... Args>
[[nodiscard]] static StepBuilder InParallel(Args&&... sequences);
// Executes each of `sequences` in parallel, independently of each other, with
// the expectation that at least one will succeed. (The others will be
// canceled.) Each sequence should be a step or MultiStep.
//
// At least one of `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.
template <typename... Args>
[[nodiscard]] static StepBuilder AnyOf(Args&&... sequences);
// 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 FormatDescription(format) on every step in `steps`.
static void AddDescription(MultiStep& steps, const base::StringPiece& format);
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 InteractiveTestT : public T, public InteractiveTestApi {
public:
template <typename... Args>
explicit InteractiveTestT(Args&&... args)
: T(std::forward<Args>(args)...),
InteractiveTestApi(std::make_unique<internal::InteractiveTestPrivate>(
std::make_unique<InteractionTestUtil>())) {}
~InteractiveTestT() override = default;
protected:
void SetUp() override {
T::SetUp();
private_test_impl().DoTestSetUp();
}
void TearDown() override {
private_test_impl().DoTestTearDown();
T::TearDown();
}
};
// A simple test fixture that brings in all of the features of
// InteractiveTestApi. No simulators are attached to test_util() so if you want
// to use verbs like PressButton() you will need to install your own simulator.
//
// Provided for convenience, but generally you will want InteractiveViewsTest
// or InteractiveBrowserTest instead.
using InteractiveTest = InteractiveTestT<testing::Test>;
// Template definitions:
// static
template <typename... 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>
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 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()");
internal::SpecifyElement(builder, 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::AfterActivate(
ElementSpecifier element,
T&& step_callback) {
StepBuilder builder;
builder.SetDescription("AfterActivate()");
internal::SpecifyElement(builder, element);
builder.SetType(InteractionSequence::StepType::kActivated);
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(), " )"}));
internal::SpecifyElement(builder, 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::IsStepCallback<T>
InteractionSequence::StepBuilder InteractiveTestApi::AfterHide(
ElementSpecifier element,
T&& step_callback) {
StepBuilder builder;
builder.SetDescription("AfterHide()");
internal::SpecifyElement(builder, element);
builder.SetType(InteractionSequence::StepType::kHidden);
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::WithElement(
ElementSpecifier element,
T&& step_callback) {
StepBuilder builder;
builder.SetDescription("WithElement()");
internal::SpecifyElement(builder, 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,
base::StringPiece name,
C&& find_callback) {
StepBuilder builder;
builder.SetDescription(
base::StringPrintf("NameElementRelative( \"%s\" )", name.data()));
ui::test::internal::SpecifyElement(builder, 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 T>
InteractionSequence::StepBuilder InteractiveTestApi::InAnyContext(T&& step) {
return std::move(step.SetContext(InteractionSequence::ContextMode::kAny)
.FormatDescription("InAnyContext( %s )"));
}
// static
template <typename T>
InteractionSequence::StepBuilder InteractiveTestApi::InSameContext(T&& step) {
return std::move(
step.SetContext(InteractionSequence::ContextMode::kFromPreviousStep)
.FormatDescription("InSameContext( %s )"));
}
template <typename T>
InteractionSequence::StepBuilder InteractiveTestApi::InContext(
ElementContext context,
T&& step) {
const auto fmt = base::StringPrintf("InContext( %p, %%s )",
static_cast<const void*>(context));
return std::move(step.SetContext(context).FormatDescription(fmt));
}
// static
template <typename C, typename T, typename E>
requires internal::IsCheckCallback<C, bool>
InteractionSequence::StepBuilder InteractiveTestApi::IfElement(
ElementSpecifier element,
C&& condition,
T&& then_steps,
E&& else_steps) {
auto step = IfElementMatches(element, std::forward<C>(condition), true,
std::forward<T>(then_steps),
std::forward<E>(else_steps));
step.SetDescription("IfElement()");
return step;
}
// static
template <typename F, typename M, typename T, typename E, typename R>
requires internal::IsCheckCallback<F, R>
InteractionSequence::StepBuilder InteractiveTestApi::IfElementMatches(
ElementSpecifier element,
F&& function,
M&& matcher,
T&& then_steps,
E&& else_steps) {
InteractionSequence::StepBuilder step;
internal::SpecifyElement(step, element);
using FunctionType =
base::OnceCallback<R(const InteractionSequence*, const TrackedElement*)>;
step.SetSubsequenceMode(InteractionSequence::SubsequenceMode::kAtMostOne);
step.AddSubsequence(
internal::BuildSubsequence(Steps(std::forward<T>(then_steps))),
base::BindOnce(
[](FunctionType function, testing::Matcher<R> matcher,
const InteractionSequence* seq, const TrackedElement* el) -> bool {
return matcher.Matches(std::move(function).Run(seq, el));
},
base::RectifyCallback<FunctionType>(
internal::MaybeBind(std::forward<F>(function))),
std::forward<M>(matcher)));
auto temp = Steps(std::forward<E>(else_steps));
if (!temp.empty()) {
step.AddSubsequence(internal::BuildSubsequence(std::move(temp)));
}
step.SetDescription("IfElementMatches()");
return step;
}
// static
template <typename C, typename T, typename E>
requires internal::HasSignature<C, bool()>
InteractionSequence::StepBuilder InteractiveTestApi::If(C&& condition,
T&& then_steps,
E&& else_steps) {
auto step =
IfMatches(std::forward<C>(condition), true, std::forward<T>(then_steps),
std::forward<E>(else_steps));
step.SetDescription("If()");
return step;
}
// static
template <typename F, typename M, typename T, typename E, typename R>
requires internal::HasCompatibleSignature<F, R(const InteractionSequence*)>
InteractionSequence::StepBuilder InteractiveTestApi::IfMatches(F&& function,
M&& matcher,
T&& then_steps,
E&& 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::forward<T>(then_steps),
std::forward<E>(else_steps));
step.SetDescription("IfMatches()");
return step;
}
// static
template <typename... Args>
InteractionSequence::StepBuilder InteractiveTestApi::InParallel(
Args&&... sequences) {
InteractionSequence::StepBuilder step;
step.SetElementID(internal::kInteractiveTestPivotElementId);
step.SetSubsequenceMode(InteractionSequence::SubsequenceMode::kAll);
(step.AddSubsequence(
internal::BuildSubsequence(Steps(std::forward<Args>(sequences)))),
...);
step.SetDescription("InParallel()");
return step;
}
// static
template <typename... Args>
InteractionSequence::StepBuilder InteractiveTestApi::AnyOf(
Args&&... sequences) {
InteractionSequence::StepBuilder step;
step.SetElementID(internal::kInteractiveTestPivotElementId);
step.SetSubsequenceMode(InteractionSequence::SubsequenceMode::kAtLeastOne);
(step.AddSubsequence(
internal::BuildSubsequence(Steps(std::forward<Args>(sequences)))),
...);
step.SetDescription("AnyOf()");
return step;
}
template <typename ObserverBase, typename Observer>
requires std::derived_from<Observer, ObserverBase> &&
internal::IsValidMatcherType<typename Observer::ValueType>
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 internal::IsValidMatcherType<typename Observer::ValueType>
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>
requires internal::IsValidMatcherType<T>
InteractionSequence::StepBuilder InteractiveTestApi::PollState(
StateIdentifier<PollingStateObserver<T>> id,
C&& callback,
base::TimeDelta polling_interval) {
using Cb = PollingStateObserver<T>::PollCallback;
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>
requires internal::IsValidMatcherType<T>
InteractionSequence::StepBuilder InteractiveTestApi::PollElement(
StateIdentifier<PollingElementStateObserver<T>> id,
ui::ElementIdentifier element_identifier,
C&& callback,
base::TimeDelta polling_interval) {
using Cb = PollingElementStateObserver<T>::PollElementCallback;
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>
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;
}
if constexpr (internal::IsReferenceWrapper<U>) {
typed->SetTarget(testing::Matcher<T>(T(value.get())));
} else if constexpr (std::derived_from<U, testing::Matcher<T>>) {
// Note that a Matcher<T> is actually a wrapper around a "matcher"
// object, not a matcher itself.
typed->SetTarget(value);
} else if constexpr (internal::IsMatcher<U>) {
// Need to wrap the "matcher" in a Matcher<T> for it to be used.
typed->SetTarget(testing::Matcher<T>(value));
} else {
typed->SetTarget(testing::Matcher<T>(
T(internal::UnwrapArgument<U>(std::move(value)))));
}
},
id.identifier(), U(std::forward<V>(value)));
auto result = Steps(WithElement(internal::kInteractiveTestPivotElementId,
std::move(wait_callback)),
WaitForShow(id.identifier()));
AddDescription(result, "WaitForState( %s )");
return result;
}
template <typename 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;
}
// 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) {
return std::move(Check(base::BindOnce(
[](base::OnceCallback<R()> function,
testing::Matcher<R> matcher) {
return internal::MatchAndExplain(
"CheckResult()", matcher,
std::move(function).Run());
},
internal::MaybeBind(std::forward<C>(function)),
testing::Matcher<R>(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()");
internal::SpecifyElement(builder, element);
builder.SetStartCallback(base::BindOnce(
[](base::OnceCallback<R(TrackedElement*)> function,
testing::Matcher<R> matcher, InteractionSequence* seq,
TrackedElement* el) {
if (!internal::MatchAndExplain("CheckElement()", matcher,
std::move(function).Run(el))) {
seq->FailForTesting();
}
},
internal::MaybeBind(std::forward<F>(function)),
testing::Matcher<R>(std::forward<M>(matcher))));
return builder;
}
} // namespace ui::test
#endif // UI_BASE_INTERACTION_INTERACTIVE_TEST_H_