| // 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_internal.h" |
| |
| #include <memory> |
| |
| #include "base/callback_list.h" |
| #include "base/check.h" |
| #include "base/containers/contains.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/overloaded.h" |
| #include "base/strings/string_piece_forward.h" |
| #include "base/strings/stringprintf.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_test_util.h" |
| #include "ui/base/interaction/framework_specific_implementation.h" |
| |
| namespace ui::test::internal { |
| |
| DEFINE_ELEMENT_IDENTIFIER_VALUE(kInteractiveTestPivotElementId); |
| DEFINE_CUSTOM_ELEMENT_EVENT_TYPE(kInteractiveTestPivotEventType); |
| |
| const char kInteractiveTestFailedMessagePrefix[] = "Interactive test failed "; |
| const char kNoCheckDescriptionSpecified[] = "[no description specified]"; |
| |
| StateObserverElement::StateObserverElement(ElementIdentifier id, |
| ElementContext context) |
| : TestElementBase(id, context) {} |
| |
| StateObserverElement::~StateObserverElement() = default; |
| |
| DEFINE_FRAMEWORK_SPECIFIC_METADATA(StateObserverElement) |
| |
| InteractiveTestPrivate::InteractiveTestPrivate( |
| std::unique_ptr<InteractionTestUtil> test_util) |
| : test_util_(std::move(test_util)) {} |
| InteractiveTestPrivate::~InteractiveTestPrivate() = default; |
| |
| void InteractiveTestPrivate::Init(ElementContext initial_context) { |
| success_ = false; |
| sequence_skipped_ = false; |
| MaybeAddPivotElement(initial_context); |
| for (ElementContext context : |
| ElementTracker::GetElementTracker()->GetAllContextsForTesting()) { |
| MaybeAddPivotElement(context); |
| } |
| context_subscription_ = |
| ElementTracker::GetElementTracker()->AddAnyElementShownCallbackForTesting( |
| base::BindRepeating(&InteractiveTestPrivate::OnElementAdded, |
| base::Unretained(this))); |
| } |
| |
| void InteractiveTestPrivate::Cleanup() { |
| context_subscription_ = base::CallbackListSubscription(); |
| pivot_elements_.clear(); |
| } |
| |
| void InteractiveTestPrivate::OnElementAdded(TrackedElement* el) { |
| if (el->identifier() == kInteractiveTestPivotElementId) |
| return; |
| MaybeAddPivotElement(el->context()); |
| } |
| |
| void InteractiveTestPrivate::MaybeAddPivotElement(ElementContext context) { |
| if (!base::Contains(pivot_elements_, context)) { |
| auto pivot = |
| std::make_unique<TestElement>(kInteractiveTestPivotElementId, context); |
| auto* const el = pivot.get(); |
| pivot_elements_.emplace(context, std::move(pivot)); |
| el->Show(); |
| } |
| } |
| |
| void InteractiveTestPrivate::HandleActionResult( |
| InteractionSequence* seq, |
| const TrackedElement* el, |
| const std::string& operation_name, |
| ActionResult result) { |
| switch (result) { |
| case ActionResult::kSucceeded: |
| break; |
| case ActionResult::kFailed: |
| LOG(ERROR) << operation_name << " failed for " << *el; |
| seq->FailForTesting(); |
| break; |
| case ActionResult::kNotAttempted: |
| LOG(ERROR) << operation_name << " could not be applied to " << *el; |
| seq->FailForTesting(); |
| break; |
| case ActionResult::kKnownIncompatible: |
| LOG(WARNING) << operation_name |
| << " failed because it is unsupported on this platform for " |
| << *el; |
| if (!on_incompatible_action_reason_.empty()) { |
| LOG(WARNING) << "Unsupported action was expected: " |
| << on_incompatible_action_reason_; |
| } else { |
| LOG(ERROR) << "Unsupported action was unexpected. " |
| "Did you forget to call SetOnIncompatibleAction()?"; |
| } |
| switch (on_incompatible_action_) { |
| case OnIncompatibleAction::kFailTest: |
| seq->FailForTesting(); |
| break; |
| case OnIncompatibleAction::kSkipTest: |
| case OnIncompatibleAction::kHaltTest: |
| sequence_skipped_ = true; |
| seq->FailForTesting(); |
| break; |
| case OnIncompatibleAction::kIgnoreAndContinue: |
| break; |
| } |
| break; |
| } |
| } |
| |
| TrackedElement* InteractiveTestPrivate::GetPivotElement( |
| ElementContext context) const { |
| const auto it = pivot_elements_.find(context); |
| CHECK(it != pivot_elements_.end()) |
| << "Tried to reference non-existent context."; |
| return it->second.get(); |
| } |
| |
| void InteractiveTestPrivate::DoTestSetUp() {} |
| void InteractiveTestPrivate::DoTestTearDown() { |
| state_observer_elements_.clear(); |
| } |
| |
| void InteractiveTestPrivate::OnSequenceComplete() { |
| success_ = true; |
| } |
| |
| void InteractiveTestPrivate::OnSequenceAborted( |
| const InteractionSequence::AbortedData& data) { |
| if (aborted_callback_for_testing_) { |
| std::move(aborted_callback_for_testing_).Run(data); |
| return; |
| } |
| if (sequence_skipped_) { |
| LOG(WARNING) << kInteractiveTestFailedMessagePrefix << data; |
| if (on_incompatible_action_ == OnIncompatibleAction::kSkipTest) { |
| GTEST_SKIP(); |
| } else { |
| DCHECK_EQ(OnIncompatibleAction::kHaltTest, on_incompatible_action_); |
| } |
| } else { |
| GTEST_FAIL() << "Interactive test failed " << data; |
| } |
| } |
| |
| void SpecifyElement(ui::InteractionSequence::StepBuilder& builder, |
| ElementSpecifier element) { |
| absl::visit( |
| base::Overloaded{ |
| [&builder](ElementIdentifier id) { builder.SetElementID(id); }, |
| [&builder](base::StringPiece name) { builder.SetElementName(name); }}, |
| element); |
| } |
| |
| std::string DescribeElement(ElementSpecifier element) { |
| return absl::visit( |
| base::Overloaded{[](ElementIdentifier id) { return id.GetName(); }, |
| [](base::StringPiece name) { |
| return base::StringPrintf("\"%s\"", name.data()); |
| }}, |
| element); |
| } |
| |
| InteractionSequence::Builder BuildSubsequence( |
| InteractiveTestPrivate::MultiStep steps) { |
| InteractionSequence::Builder builder; |
| for (auto& step : steps) { |
| builder.AddStep(std::move(step)); |
| } |
| return builder; |
| } |
| |
| } // namespace ui::test::internal |