blob: 4080fc2b2d3095217f02283e277faa807ea2692b [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.
#include "ui/base/interaction/interactive_test.h"
#include <memory>
#include <string>
#include "base/auto_reset.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_test_util.h"
#include "ui/base/interaction/interaction_sequence.h"
#include "ui/base/interaction/interaction_test_util.h"
#include "ui/base/interaction/interactive_test_internal.h"
namespace ui::test {
using internal::kInteractiveTestPivotElementId;
InteractiveTestApi::InteractiveTestApi(
std::unique_ptr<internal::InteractiveTestPrivate> private_test_impl)
: private_test_impl_(std::move(private_test_impl)) {}
InteractiveTestApi::~InteractiveTestApi() = default;
InteractionSequence::StepBuilder InteractiveTestApi::PressButton(
ElementSpecifier button,
InputType input_type) {
StepBuilder builder;
builder.SetDescription("PressButton()");
internal::SpecifyElement(builder, button);
builder.SetMustRemainVisible(false);
builder.SetStartCallback(base::BindOnce(
[](InputType input_type, InteractiveTestApi* test,
InteractionSequence* seq, TrackedElement* el) {
test->private_test_impl().HandleActionResult(
seq, el, "PressButton",
test->test_util().PressButton(el, input_type));
},
input_type, base::Unretained(this)));
return builder;
}
InteractionSequence::StepBuilder InteractiveTestApi::SelectMenuItem(
ElementSpecifier menu_item,
InputType input_type) {
StepBuilder builder;
builder.SetDescription("SelectMenuItem()");
internal::SpecifyElement(builder, menu_item);
builder.SetMustRemainVisible(false);
builder.SetStartCallback(base::BindOnce(
[](InputType input_type, InteractiveTestApi* test,
InteractionSequence* seq, TrackedElement* el) {
test->private_test_impl().HandleActionResult(
seq, el, "SelectMenuItem",
test->test_util().SelectMenuItem(el, input_type));
},
input_type, base::Unretained(this)));
return builder;
}
InteractionSequence::StepBuilder InteractiveTestApi::DoDefaultAction(
ElementSpecifier element,
InputType input_type) {
StepBuilder builder;
builder.SetDescription("DoDefaultAction()");
internal::SpecifyElement(builder, element);
builder.SetMustRemainVisible(false);
builder.SetStartCallback(base::BindOnce(
[](InputType input_type, InteractiveTestApi* test,
InteractionSequence* seq, TrackedElement* el) {
test->private_test_impl().HandleActionResult(
seq, el, "DoDefaultAction",
test->test_util().DoDefaultAction(el, input_type));
},
input_type, base::Unretained(this)));
return builder;
}
InteractionSequence::StepBuilder InteractiveTestApi::SelectTab(
ElementSpecifier tab_collection,
size_t tab_index,
InputType input_type) {
StepBuilder builder;
builder.SetDescription(base::StringPrintf("SelectTab( %zu )", tab_index));
internal::SpecifyElement(builder, tab_collection);
builder.SetStartCallback(base::BindOnce(
[](size_t index, InputType input_type, InteractiveTestApi* test,
InteractionSequence* seq, TrackedElement* el) {
test->private_test_impl().HandleActionResult(
seq, el, "SelectTab",
test->test_util().SelectTab(el, index, input_type));
},
tab_index, input_type, base::Unretained(this)));
return builder;
}
InteractionSequence::StepBuilder InteractiveTestApi::SelectDropdownItem(
ElementSpecifier collection,
size_t item,
InputType input_type) {
StepBuilder builder;
builder.SetDescription(base::StringPrintf("SelectDropdownItem( %zu )", item));
internal::SpecifyElement(builder, collection);
builder.SetStartCallback(base::BindOnce(
[](size_t item, InputType input_type, InteractiveTestApi* test,
InteractionSequence* seq, TrackedElement* el) {
test->private_test_impl().HandleActionResult(
seq, el, "SelectDropdownItem",
test->test_util().SelectDropdownItem(el, item, input_type));
},
item, input_type, base::Unretained(this)));
return builder;
}
InteractionSequence::StepBuilder InteractiveTestApi::EnterText(
ElementSpecifier element,
std::u16string text,
TextEntryMode mode) {
StepBuilder builder;
builder.SetDescription(base::StringPrintf("EnterText( \"%s\" )",
base::UTF16ToUTF8(text).c_str()));
internal::SpecifyElement(builder, element);
builder.SetStartCallback(base::BindOnce(
[](std::u16string text, TextEntryMode mode, InteractiveTestApi* test,
InteractionSequence* seq, TrackedElement* el) {
test->private_test_impl().HandleActionResult(
seq, el, "EnterText",
test->test_util().EnterText(el, std::move(text), mode));
},
std::move(text), mode, base::Unretained(this)));
return builder;
}
InteractionSequence::StepBuilder InteractiveTestApi::ActivateSurface(
ElementSpecifier element) {
StepBuilder builder;
builder.SetDescription("ActivateSurface()");
internal::SpecifyElement(builder, element);
builder.SetStartCallback(base::BindOnce(
[](InteractiveTestApi* test, InteractionSequence* seq,
TrackedElement* el) {
test->private_test_impl().HandleActionResult(
seq, el, "ActivateSurface", test->test_util().ActivateSurface(el));
},
base::Unretained(this)));
return builder;
}
#if !BUILDFLAG(IS_IOS)
InteractionSequence::StepBuilder InteractiveTestApi::SendAccelerator(
ElementSpecifier element,
Accelerator accelerator) {
StepBuilder builder;
builder.SetDescription(base::StringPrintf(
"SendAccelerator( %s )",
base::UTF16ToUTF8(accelerator.GetShortcutText()).c_str()));
internal::SpecifyElement(builder, element);
builder.SetStartCallback(base::BindOnce(
[](Accelerator accelerator, InteractiveTestApi* test,
InteractionSequence* seq, TrackedElement* el) {
test->private_test_impl().HandleActionResult(
seq, el, "SendAccelerator",
test->test_util().SendAccelerator(el, accelerator));
},
accelerator, base::Unretained(this)));
return builder;
}
#endif // !BUILDFLAG(IS_IOS)
InteractionSequence::StepBuilder InteractiveTestApi::Confirm(
ElementSpecifier element) {
StepBuilder builder;
builder.SetDescription("Confirm()");
internal::SpecifyElement(builder, element);
builder.SetStartCallback(base::BindOnce(
[](InteractiveTestApi* test, InteractionSequence* seq,
TrackedElement* el) {
test->private_test_impl().HandleActionResult(
seq, el, "Confirm", test->test_util().Confirm(el));
},
base::Unretained(this)));
return builder;
}
// static
InteractionSequence::StepBuilder InteractiveTestApi::WaitForShow(
ElementSpecifier element,
bool transition_only_on_event) {
StepBuilder step;
step.SetDescription("WaitForShow()");
internal::SpecifyElement(step, element);
step.SetTransitionOnlyOnEvent(transition_only_on_event);
return step;
}
// static
InteractionSequence::StepBuilder InteractiveTestApi::WaitForHide(
ElementSpecifier element,
bool transition_only_on_event) {
StepBuilder step;
step.SetDescription("WaitForHide()");
internal::SpecifyElement(step, element);
step.SetType(InteractionSequence::StepType::kHidden);
step.SetTransitionOnlyOnEvent(transition_only_on_event);
return step;
}
// static
InteractionSequence::StepBuilder InteractiveTestApi::WaitForActivate(
ElementSpecifier element) {
StepBuilder step;
step.SetDescription("WaitForActivate()");
internal::SpecifyElement(step, element);
step.SetType(InteractionSequence::StepType::kActivated);
return step;
}
// static
InteractionSequence::StepBuilder InteractiveTestApi::WaitForEvent(
ElementSpecifier element,
CustomElementEventType event) {
StepBuilder step;
step.SetDescription(
base::StringPrintf("WaitForEvent( %s )", event.GetName().c_str()));
internal::SpecifyElement(step, element);
step.SetType(InteractionSequence::StepType::kCustomEvent, event);
return step;
}
// static
InteractiveTestApi::MultiStep InteractiveTestApi::EnsureNotPresent(
ElementIdentifier element_to_check,
bool in_any_context) {
return internal::InteractiveTestPrivate::PostTask(
base::StringPrintf("EnsureNotPresent( %s, %d )",
element_to_check.GetName().c_str(), in_any_context),
base::BindOnce(
[](ElementIdentifier element_to_check, bool in_any_context,
InteractionSequence* seq, TrackedElement* reference) {
auto* const element =
in_any_context
? ElementTracker::GetElementTracker()
->GetElementInAnyContext(element_to_check)
: ElementTracker::GetElementTracker()
->GetFirstMatchingElement(element_to_check,
reference->context());
if (element) {
LOG(ERROR) << "Expected element " << element_to_check
<< " not to be present but it was present.";
seq->FailForTesting();
}
},
element_to_check, in_any_context));
}
// static
InteractiveTestApi::MultiStep InteractiveTestApi::EnsurePresent(
ElementSpecifier element_to_check,
bool in_any_context) {
return Steps(
FlushEvents(),
std::move(
WithElement(element_to_check, base::DoNothing())
.SetDescription(base::StringPrintf(
"EnsurePresent( %s, %d )",
internal::DescribeElement(element_to_check).c_str(),
in_any_context))
.SetContext(in_any_context
? InteractionSequence::ContextMode::kAny
: InteractionSequence::ContextMode::kInitial)));
}
// static
InteractiveTestApi::MultiStep InteractiveTestApi::FlushEvents() {
return internal::InteractiveTestPrivate::PostTask("FlushEvents()",
base::DoNothing());
}
// static
InteractiveTestApi::MultiStep InteractiveTestApi::InAnyContext(
MultiStep steps) {
for (auto& step : steps) {
step.SetContext(InteractionSequence::ContextMode::kAny)
.FormatDescription("InAnyContext( %s )");
}
return steps;
}
// static
InteractiveTestApi::MultiStep InteractiveTestApi::InSameContext(
MultiStep steps) {
for (auto& step : steps) {
step.SetContext(InteractionSequence::ContextMode::kFromPreviousStep)
.FormatDescription("InSameContext( %s )");
}
return steps;
}
// static
InteractiveTestApi::MultiStep InteractiveTestApi::InContext(
ElementContext context,
MultiStep steps) {
// This context may not yet exist, but we want the pivot element to exist.
private_test_impl_->MaybeAddPivotElement(context);
const auto fmt = base::StringPrintf("InContext( %p, %%s )",
static_cast<const void*>(context));
for (auto& step : steps) {
step.SetContext(context).FormatDescription(fmt);
}
return steps;
}
InteractiveTestApi::StepBuilder InteractiveTestApi::SetOnIncompatibleAction(
OnIncompatibleAction action,
const char* reason) {
return Do(base::BindOnce(
[](InteractiveTestApi* test, OnIncompatibleAction action,
std::string reason) {
DCHECK(action == OnIncompatibleAction::kFailTest || !reason.empty());
test->private_test_impl().on_incompatible_action_ = action;
test->private_test_impl().on_incompatible_action_reason_ = reason;
},
base::Unretained(this), action, std::string(reason)));
}
bool InteractiveTestApi::RunTestSequenceImpl(
ElementContext context,
InteractionSequence::Builder builder) {
builder.SetContext(context);
private_test_impl_->Init(context);
builder.SetCompletedCallback(
base::BindOnce(&internal::InteractiveTestPrivate::OnSequenceComplete,
base::Unretained(private_test_impl_.get())));
builder.SetAbortedCallback(
base::BindOnce(&internal::InteractiveTestPrivate::OnSequenceAborted,
base::Unretained(private_test_impl_.get())));
auto sequence = builder.Build();
{
base::test::ScopedRunLoopTimeout timeout(
FROM_HERE, absl::nullopt,
base::BindRepeating(
[](base::WeakPtr<InteractionSequence> sequence) {
std::ostringstream oss;
if (sequence) {
oss << internal::kInteractiveTestFailedMessagePrefix
<< sequence->BuildAbortedData(
InteractionSequence::AbortedReason::
kSequenceTimedOut);
} else {
oss << "Interactive test: timeout after test sequence "
"destroyed; a failure message may already have been "
"logged.";
}
return oss.str();
},
sequence->AsWeakPtr()));
sequence->RunSynchronouslyForTesting();
}
private_test_impl_->Cleanup();
return private_test_impl_->success_;
}
// static
void InteractiveTestApi::AddStep(InteractionSequence::Builder& builder,
MultiStep multi_step) {
for (auto& step : multi_step)
builder.AddStep(step);
}
// static
void InteractiveTestApi::AddStep(MultiStep& dest, StepBuilder src) {
dest.emplace_back(std::move(src));
}
// static
void InteractiveTestApi::AddStep(MultiStep& dest, MultiStep src) {
for (auto& step : src)
dest.emplace_back(std::move(step));
}
} // namespace ui::test