diff --git a/chrome/browser/ash/crosapi/test_controller_ash.cc b/chrome/browser/ash/crosapi/test_controller_ash.cc index ec3cf98af6d325..f7c8f4bf36add2 100644 --- a/chrome/browser/ash/crosapi/test_controller_ash.cc +++ b/chrome/browser/ash/crosapi/test_controller_ash.cc @@ -61,6 +61,7 @@ #include "ui/events/event_source.h" #include "ui/events/types/event_type.h" #include "ui/gfx/geometry/point.h" +#include "ui/views/controls/button/button.h" #include "ui/views/interaction/element_tracker_views.h" #include "ui/views/interaction/interaction_test_util_views.h" @@ -137,20 +138,19 @@ void TestControllerAsh::ClickElement(const std::string& element_name, } // Pick the first view that matches the element name. - views::View* view = views[0]; + views::Button* button = views::Button::AsButton(views[0]); + if (!button) { + std::move(callback).Run(/*success=*/false); + return; + } // We directly send mouse events to the view. It's also possible to use // EventGenerator to move the mouse and send a click. Unfortunately, that // approach has occasional flakiness. This is presumably due to another window // appearing on top of the dialog and taking the mouse events but has not been // explicitly diagnosed. - views::TrackedElementViews* tracked_element = - views::ElementTrackerViews::GetInstance()->GetElementForView( - view, /*assign_temporary_id=*/false); - views::test::InteractionTestUtilSimulatorViews simulator; - simulator.PressButton(tracked_element, - ui::test::InteractionTestUtil::InputType::kMouse); - + views::test::InteractionTestUtilSimulatorViews::PressButton( + button, ui::test::InteractionTestUtil::InputType::kMouse); std::move(callback).Run(/*success=*/true); } diff --git a/chrome/browser/ui/views/user_education/help_bubble_factory_registry_interactive_uitest.cc b/chrome/browser/ui/views/user_education/help_bubble_factory_registry_interactive_uitest.cc index 667bfef4f4de38..d96c6ed218da5a 100644 --- a/chrome/browser/ui/views/user_education/help_bubble_factory_registry_interactive_uitest.cc +++ b/chrome/browser/ui/views/user_education/help_bubble_factory_registry_interactive_uitest.cc @@ -69,7 +69,8 @@ IN_PROC_BROWSER_TEST_F(HelpBubbleFactoryRegistryInteractiveUitest, kAppMenuButtonElementId, context); ASSERT_NE(nullptr, app_menu_button); InteractionTestUtilBrowser test_util; - test_util.PressButton(app_menu_button); + ASSERT_EQ(ui::test::ActionResult::kSucceeded, + test_util.PressButton(app_menu_button)); // Verify that the history menu item is visible. ui::TrackedElement* const element = diff --git a/chrome/browser/ui/views/user_education/tutorial_interactive_uitest.cc b/chrome/browser/ui/views/user_education/tutorial_interactive_uitest.cc index 26bdcd90766a13..50172672f1bfc2 100644 --- a/chrome/browser/ui/views/user_education/tutorial_interactive_uitest.cc +++ b/chrome/browser/ui/views/user_education/tutorial_interactive_uitest.cc @@ -106,7 +106,8 @@ IN_PROC_BROWSER_TEST_F(TutorialInteractiveUitest, SampleTutorial) { GetElement(kTabStripElementId), kCustomEventType1); InteractionTestUtilBrowser test_util; - test_util.PressButton(GetElement(kAppMenuButtonElementId)); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util.PressButton(GetElement(kAppMenuButtonElementId))); // Simulate click on close button. EXPECT_CALL_IN_SCOPE( diff --git a/chrome/test/interaction/README.md b/chrome/test/interaction/README.md index d266d7aabbcf2e..ae013027e07e56 100644 --- a/chrome/test/interaction/README.md +++ b/chrome/test/interaction/README.md @@ -81,7 +81,8 @@ Verbs fall into a number of different categories: - `CheckView()` [Views] - `CheckViewProperty()` [Views] - `Screenshot` [Browser] - compares the target against Skia Gold in pixel - tests + tests. See [Handling Incompatibilities](#handling-incompatibilities) for + how to handle this in non-pixel tests. - **WaitFor** verbs ensure that the given UI event happens or condition becomes true before proceeding. Examples: - `WaitForShow()` @@ -118,10 +119,13 @@ Verbs fall into a number of different categories: - `SelectTab()` - `SelectDropdownItem()` - `EnterText()` - - `ActivateSurface()` - `SendAccelerator()` - `Confirm()` - `DoDefaultAction()` + - `ActivateSurface()` + - ActivateSurface is not always reliable on Linux with the Wayland window + manager; see [Handling Incompatibilities](#handling-incompatibilities) + for how to correctly deal with this. - **Mouse** verbs simulate mouse input to the entire application, and are therefore only reliable in test fixtures that run as exclusive processes (e.g. interactive_browser_tests). Examples include: @@ -160,9 +164,14 @@ Verbs fall into a number of different categories: - `ExecuteJsAt()` [Browser] - `CheckJsResult()` [Browser] - `CheckJsResultAt()` [Browser] -- Utility verbs modify how the test sequence is executed. Currently there is - only `FlushEvents()`, which ensures that the next step happens on a fresh - message loop rather than being able to chain successive steps. +- **Utility** verbs modify how the test sequence is executed. + - `FlushEvents()` - ensures that the next step happens on a fresh + message loop rather than being able to chain successive steps. + - `SetOnIncompatibleAction()` changes what the sequence will do when faced + with an action that cannot be executed on the current + build, environment, or platform. See + [Handling Incompatibilities](#handling-incompatibilities) for more + information and best practices. Example with mouse input: ```cpp @@ -235,6 +244,45 @@ RunTestSequence( WaitForShow(kDownloadsMenuItemElementId)))); ``` +### Handling Incompatibilities + +Sometimes a test won't run on a specific build bot or in a specific environment +due to a known incompatibility (as opposed to something legitimately failing). +Current known incompatibilities include: + - `ActivateSurface()` does not work on the `linux-wayland` buildbot unless the + surface is already active, due to vanilla Wayland not supporting programmatic + window activation. + - `Screenshot()` only works in specific pixel test jobs on the `win-rel` + buildbot. + +Normally, if you know that the test won't run on an entire platform (i.e. you +can use `BUILDFLAG()` to differentiate) you should disable or skip the tests in +the usual way. But if the distinction is finer-grained (as with the above verbs) +The `SetOnIncompatibleAction()` verb and `OnIncompatibleAction` enumeration are +provided. + - `OnIncompatibleAction::kFailTest` is the default option; if a step fails + because of a known incompatibility, the test will fail, and an error message + will be printed. + - `OnIncompatibleAction::kSkipTest` immediately skips the test as soon as an + incompatibility is detected. Use this option when you know the rest of the + test will fail and the test results are invalid. A warning will be printed. + - `OnIncompatibleAction::kHaltTest` immediately halts the sequence but does not + fail or skip the test. Use this option when all of the steps leading up to + the incompatible one are valid and you want to preserve any non-fatal errors + that may have occurred. A warning will still be printed. + - `OnIncompatibleAction::kIgnoreAndContinue` skips the problematic step, prints + a warning, and continues the test as if nothing happened. Use this option + when the step is incidental to the test, such as taking a screenshot in the + middle of a sequence. + +***Do not use `SetOnIncompatibleAction()` unless:*** + 1. You know the test will fail due to a known incompatibility. + 2. The test cannot be disabled or skipped using a simple `BUILDFLAG()` check. + +Note that you *must* specify a non-empty `reason` when calling +`SetOnIncompatibleAction()` with any argument except `kFailTest`. This string +will be printed out as part of the warning that is produced if the step fails. + ### WebContents Instrumentation A feature of `InteractiveBrowserTestApi` that it borrows from diff --git a/chrome/test/interaction/interaction_test_util_browser.cc b/chrome/test/interaction/interaction_test_util_browser.cc index b60b04fea75d06..69db1f8848fb16 100644 --- a/chrome/test/interaction/interaction_test_util_browser.cc +++ b/chrome/test/interaction/interaction_test_util_browser.cc @@ -8,6 +8,7 @@ #include "base/strings/strcat.h" #include "build/build_config.h" +#include "build/chromeos_buildflags.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_window.h" @@ -18,6 +19,7 @@ #include "chrome/test/interaction/tracked_element_webcontents.h" #include "chrome/test/interaction/webcontents_interaction_test_util.h" #include "ui/base/interaction/element_tracker.h" +#include "ui/base/interaction/interaction_test_util.h" #include "ui/base/test/ui_controls.h" #include "ui/events/event.h" #include "ui/events/types/event_type.h" @@ -33,6 +35,7 @@ #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) #define SUPPORTS_PIXEL_TESTS 1 +#include "base/command_line.h" #include "chrome/browser/ui/test/test_browser_ui.h" #else #define SUPPORTS_PIXEL_TESTS 0 @@ -86,29 +89,32 @@ class InteractionTestUtilSimulatorBrowser // Browser accelerators must be sent via key events to the window on Mac or // they don't work properly. Dialog accelerators still appear to work the same // as on other platforms. - bool SendAccelerator(ui::TrackedElement* element, - const ui::Accelerator& accelerator) override { + ui::test::ActionResult SendAccelerator(ui::TrackedElement* element, + ui::Accelerator accelerator) override { Browser* const browser = InteractionTestUtilBrowser::GetBrowserFromContext(element->context()); if (!browser) - return false; - - CHECK(ui_controls::SendKeyPress( - browser->window()->GetNativeWindow(), accelerator.key_code(), - accelerator.IsCtrlDown(), accelerator.IsShiftDown(), - accelerator.IsAltDown(), accelerator.IsCmdDown())); - - return true; + return ui::test::ActionResult::kNotAttempted; + + if (!ui_controls::SendKeyPress( + browser->window()->GetNativeWindow(), accelerator.key_code(), + accelerator.IsCtrlDown(), accelerator.IsShiftDown(), + accelerator.IsAltDown(), accelerator.IsCmdDown())) { + LOG(ERROR) << "Failed to send accelerator" + << accelerator.GetShortcutText() << " to " << *element; + return ui::test::ActionResult::kFailed; + } + return ui::test::ActionResult::kSucceeded; } #endif // BUILDFLAG(IS_MAC) - bool SelectTab(ui::TrackedElement* tab_collection, - size_t index, - InputType input_type) override { + ui::test::ActionResult SelectTab(ui::TrackedElement* tab_collection, + size_t index, + InputType input_type) override { // This handler *explicitly* only handles Browser and TabStrip; it will // reject any other element or View type. if (!tab_collection->IsA()) - return false; + return ui::test::ActionResult::kNotAttempted; auto* const view = tab_collection->AsA()->view(); TabStrip* tab_strip = nullptr; @@ -118,36 +124,43 @@ class InteractionTestUtilSimulatorBrowser tab_strip = views::AsViewClass(view); } if (!tab_strip) - return false; + return ui::test::ActionResult::kNotAttempted; // Verify that the tab index is in range; at this point it's a fatal error // if it's out of bounds. - CHECK_LT(static_cast(index), tab_strip->GetTabCount()) - << "Tab strip tab index " << index << " is out of bounds."; + if (static_cast(index) >= tab_strip->GetTabCount()) { + LOG(ERROR) << "Tabstrip index " << index + << " is out of bounds, there are " << tab_strip->GetTabCount() + << " tabs."; + return ui::test::ActionResult::kFailed; + } // Tabs can be selected using a default action; no special input logic is // needed. Tab* const tab = tab_strip->tab_at(index); views::test::InteractionTestUtilSimulatorViews::DoDefaultAction(tab, input_type); - CHECK_EQ(static_cast(index), tab_strip->GetActiveIndex().value()); - return true; + if (static_cast(index) != tab_strip->GetActiveIndex()) { + LOG(ERROR) << "Failed to select tabstrip tab " << index; + return ui::test::ActionResult::kFailed; + } + return ui::test::ActionResult::kSucceeded; } - bool Confirm(ui::TrackedElement* element) override { + ui::test::ActionResult Confirm(ui::TrackedElement* element) override { // This handler *explicitly* only handles OmniboxView; it will reject any // other element or View type. if (!element->IsA()) - return false; + return ui::test::ActionResult::kNotAttempted; auto* const view = element->AsA()->view(); if (auto* const omnibox = views::AsViewClass(view)) { ui::KeyEvent press(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, ui::EF_NONE); omnibox->OnKeyEvent(&press); ui::KeyEvent release(ui::ET_KEY_RELEASED, ui::VKEY_RETURN, ui::EF_NONE); omnibox->OnKeyEvent(&release); - return true; + return ui::test::ActionResult::kSucceeded; } - return false; + return ui::test::ActionResult::kNotAttempted; } }; @@ -176,23 +189,37 @@ Browser* InteractionTestUtilBrowser::GetBrowserFromContext( } // static -bool InteractionTestUtilBrowser::CompareScreenshot( +ui::test::ActionResult InteractionTestUtilBrowser::CompareScreenshot( ui::TrackedElement* element, const std::string& screenshot_name, const std::string& baseline) { -#if SUPPORTS_PIXEL_TESTS views::View* view = nullptr; if (auto* const view_el = element->AsA()) { view = view_el->view(); } else if (auto* const page_el = element->AsA()) { view = page_el->owner()->GetWebView(); } + if (!view) { + return ui::test::ActionResult::kNotAttempted; + } - CHECK(view); +#if SUPPORTS_PIXEL_TESTS + // pixel_browser_tests and pixel_interactive_ui_tests specify this command + // line, which is checked by TestBrowserUi before attempting any screen + // capture; otherwise screenshotting is a silent no-op. + if (!base::CommandLine::ForCurrentProcess()->HasSwitch( + "browser-ui-tests-verify-pixels")) { + LOG(WARNING) + << "Cannot take screenshot: pixel test command line not set. This is " + "normal for non-pixel-test jobs such as vanilla browser_tests."; + return ui::test::ActionResult::kKnownIncompatible; + } PixelTestUi pixel_test_ui(view, screenshot_name, baseline); - return pixel_test_ui.VerifyUi(); + return pixel_test_ui.VerifyUi() ? ui::test::ActionResult::kSucceeded + : ui::test::ActionResult::kFailed; #else // !SUPPORTS_PIXEL_TESTS - return true; + LOG(WARNING) << "Current platform does not support pixel tests."; + return ui::test::ActionResult::kKnownIncompatible; #endif } diff --git a/chrome/test/interaction/interaction_test_util_browser.h b/chrome/test/interaction/interaction_test_util_browser.h index 405c83d53af964..e30f421a452ff4 100644 --- a/chrome/test/interaction/interaction_test_util_browser.h +++ b/chrome/test/interaction/interaction_test_util_browser.h @@ -25,8 +25,9 @@ class InteractionTestUtilBrowser : public ui::test::InteractionTestUtil { static Browser* GetBrowserFromContext(ui::ElementContext context); // Takes a screenshot based on the contents of `element` and compares with - // Skia Gold. On platforms where screenshots are unsupported or flaky, may - // trivially return true. + // Skia Gold. May return ActionResult::kKnownIncompatible on platforms and + // builds where screenshots are not supported or not reliable. This is not + // necessarily an error; the remainder of the test may still be valid. // // The name of the screenshot will be composed as follows: // TestFixture_TestName[_screenshot_name]_baseline @@ -57,9 +58,10 @@ class InteractionTestUtilBrowser : public ui::test::InteractionTestUtil { // activation, or occlusion could change the behavior of a test). So if you // need to both test complex interaction and take screenshots, prefer putting // your test in interactive_ui_tests. - static bool CompareScreenshot(ui::TrackedElement* element, - const std::string& screenshot_name, - const std::string& baseline); + static ui::test::ActionResult CompareScreenshot( + ui::TrackedElement* element, + const std::string& screenshot_name, + const std::string& baseline); }; #endif // CHROME_TEST_INTERACTION_INTERACTION_TEST_UTIL_BROWSER_H_ diff --git a/chrome/test/interaction/interaction_test_util_browser_browsertest.cc b/chrome/test/interaction/interaction_test_util_browser_browsertest.cc index a076b25dab29a9..c622b9c98a1a2e 100644 --- a/chrome/test/interaction/interaction_test_util_browser_browsertest.cc +++ b/chrome/test/interaction/interaction_test_util_browser_browsertest.cc @@ -14,12 +14,14 @@ #include "chrome/test/interaction/webcontents_interaction_test_util.h" #include "content/public/test/browser_test.h" #include "ui/base/interaction/element_identifier.h" +#include "ui/base/interaction/interaction_test_util.h" #include "ui/base/page_transition_types.h" #include "ui/views/interaction/element_tracker_views.h" namespace { DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsElementId); constexpr char kDocumentWithTitle1URL[] = "/title1.html"; +constexpr char kSkipPixelTestsReason[] = "Should only run in pixel_tests."; } class InteractionTestUtilBrowserTest : public InteractiveBrowserTest { @@ -54,6 +56,8 @@ IN_PROC_BROWSER_TEST_F(InteractionTestUtilBrowserTest, GetBrowserFromContext) { IN_PROC_BROWSER_TEST_F(InteractionTestUtilBrowserTest, CompareScreenshot_View) { RunTestSequence( + SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest, + kSkipPixelTestsReason), // This adds a callback that calls // InteractionTestUtilBrowser::CompareScreenshot(). Screenshot(kAppMenuButtonElementId, "AppMenuButton", "3924454")); @@ -69,6 +73,8 @@ IN_PROC_BROWSER_TEST_F(InteractionTestUtilBrowserTest, const GURL url = embedded_test_server()->GetURL(kDocumentWithTitle1URL); RunTestSequence(InstrumentTab(kWebContentsElementId), + SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest, + kSkipPixelTestsReason), NavigateWebContents(kWebContentsElementId, url), // This adds a callback that calls // InteractionTestUtilBrowser::CompareScreenshot(). @@ -111,19 +117,25 @@ IN_PROC_BROWSER_TEST_P(InteractionTestUtilBrowserSelectTabTest, SelectTab) { // Select a few different tabs using both the browser and tabstrip as targets. InteractionTestUtilBrowser test_util; - test_util.SelectTab(browser_el, 2); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util.SelectTab(browser_el, 2)); EXPECT_EQ(2, tab_strip->GetActiveIndex()); - test_util.SelectTab(tabstrip_el, 1); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util.SelectTab(tabstrip_el, 1)); EXPECT_EQ(1, tab_strip->GetActiveIndex()); - test_util.SelectTab(tabstrip_el, 0); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util.SelectTab(tabstrip_el, 0)); EXPECT_EQ(0, tab_strip->GetActiveIndex()); - test_util.SelectTab(browser_el, 3); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util.SelectTab(browser_el, 3)); EXPECT_EQ(3, tab_strip->GetActiveIndex()); // Re-selecting the same tab shouldn't break anything. - test_util.SelectTab(tabstrip_el, 3); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util.SelectTab(tabstrip_el, 3)); EXPECT_EQ(3, tab_strip->GetActiveIndex()); - test_util.SelectTab(browser_el, 3); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util.SelectTab(browser_el, 3)); EXPECT_EQ(3, tab_strip->GetActiveIndex()); } diff --git a/chrome/test/interaction/interaction_test_util_browser_interactive_uitest.cc b/chrome/test/interaction/interaction_test_util_browser_interactive_uitest.cc index 0afb7c339320d6..dc4b7db2e3f55c 100644 --- a/chrome/test/interaction/interaction_test_util_browser_interactive_uitest.cc +++ b/chrome/test/interaction/interaction_test_util_browser_interactive_uitest.cc @@ -16,6 +16,7 @@ #include "chrome/test/interaction/webcontents_interaction_test_util.h" #include "content/public/test/browser_test.h" #include "ui/base/interaction/element_identifier.h" +#include "ui/base/interaction/interaction_test_util.h" #include "ui/views/interaction/element_tracker_views.h" #include "ui/views/view_utils.h" @@ -40,6 +41,9 @@ IN_PROC_BROWSER_TEST_F(InteractionTestUtilBrowserUiTest, GURL("chrome://downloads")), // This adds a callback that calls // InteractionTestUtilBrowser::CompareScreenshot(). + SetOnIncompatibleAction( + OnIncompatibleAction::kIgnoreAndContinue, + "Screenshot can only run in pixel_tests on Windows."), Screenshot(kDownloadsPageElementId, std::string(), "3924454")); } @@ -92,7 +96,11 @@ IN_PROC_BROWSER_TEST_F(InteractionTestUtilBrowserUiTest, kTabSearchPageElementId, kTabDataDisplayedEvent, base::BindLambdaForTesting([&](ui::InteractionSequence* sequence, ui::TrackedElement* element) { - EXPECT_TRUE(InteractionTestUtilBrowser::CompareScreenshot( - element, std::string(), "3664291")); + const auto result = InteractionTestUtilBrowser::CompareScreenshot( + element, std::string(), "3664291"); + EXPECT_THAT( + result, + testing::AnyOf(ui::test::ActionResult::kSucceeded, + ui::test::ActionResult::kKnownIncompatible)); }))); } diff --git a/chrome/test/interaction/interaction_test_util_browser_unittest.cc b/chrome/test/interaction/interaction_test_util_browser_unittest.cc index 0624f0e3e52ff1..7390001e58e5df 100644 --- a/chrome/test/interaction/interaction_test_util_browser_unittest.cc +++ b/chrome/test/interaction/interaction_test_util_browser_unittest.cc @@ -85,9 +85,10 @@ TEST_F(InteractionTestUtilBrowserTest, PressHoverButton) { hover_button = contents_->AddChildView(std::make_unique( views::Button::PressedCallback(pressed), u"Button")); widget_->LayoutRootViewIfNecessary(); - test_util_.PressButton( - views::ElementTrackerViews::GetInstance()->GetElementForView(hover_button, - true), - ui::test::InteractionTestUtil::InputType::kKeyboard); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_.PressButton( + views::ElementTrackerViews::GetInstance()->GetElementForView( + hover_button, true), + ui::test::InteractionTestUtil::InputType::kKeyboard)); EXPECT_EQ(nullptr, hover_button); } diff --git a/chrome/test/interaction/interaction_test_util_interactive_uitest.cc b/chrome/test/interaction/interaction_test_util_interactive_uitest.cc index 6b1d5d38aed3f2..65320286fb2bea 100644 --- a/chrome/test/interaction/interaction_test_util_interactive_uitest.cc +++ b/chrome/test/interaction/interaction_test_util_interactive_uitest.cc @@ -91,8 +91,11 @@ IN_PROC_BROWSER_TEST_F(InteractionTestUtilInteractiveUitest, #endif }); - auto click_menu_item = base::BindLambdaForTesting( - [&](ui::TrackedElement* element) { test_util_.SelectMenuItem(element); }); + auto click_menu_item = + base::BindLambdaForTesting([&](ui::TrackedElement* element) { + ASSERT_EQ(ui::test::ActionResult::kSucceeded, + test_util_.SelectMenuItem(element)); + }); auto sequence = ui::InteractionSequence::Builder() diff --git a/chrome/test/interaction/interactive_browser_test.cc b/chrome/test/interaction/interactive_browser_test.cc index 8d7a1ce6082f0f..64e54e16fae82c 100644 --- a/chrome/test/interaction/interactive_browser_test.cc +++ b/chrome/test/interaction/interactive_browser_test.cc @@ -74,15 +74,14 @@ ui::InteractionSequence::StepBuilder InteractiveBrowserTestApi::Screenshot( baseline.c_str())); ui::test::internal::SpecifyElement(builder, element); builder.SetStartCallback(base::BindOnce( - [](std::string screenshot_name, std::string baseline, - ui::InteractionSequence* seq, ui::TrackedElement* el) { - if (!InteractionTestUtilBrowser::CompareScreenshot(el, screenshot_name, - baseline)) { - LOG(ERROR) << "Screenshot failed: " << screenshot_name; - seq->FailForTesting(); - } + [](InteractiveBrowserTestApi* test, std::string screenshot_name, + std::string baseline, ui::InteractionSequence* seq, + ui::TrackedElement* el) { + const auto result = InteractionTestUtilBrowser::CompareScreenshot( + el, screenshot_name, baseline); + test->test_impl().HandleActionResult(seq, el, "Screenshot", result); }, - screenshot_name, baseline)); + base::Unretained(this), screenshot_name, baseline)); return builder; } diff --git a/chrome/test/interaction/tracked_element_webcontents.cc b/chrome/test/interaction/tracked_element_webcontents.cc index 4bbb766fa87ebe..3b42cc318ca349 100644 --- a/chrome/test/interaction/tracked_element_webcontents.cc +++ b/chrome/test/interaction/tracked_element_webcontents.cc @@ -5,6 +5,9 @@ #include "chrome/test/interaction/tracked_element_webcontents.h" +#include "chrome/test/interaction/webcontents_interaction_test_util.h" +#include "content/public/browser/web_contents.h" + TrackedElementWebContents::TrackedElementWebContents( ui::ElementIdentifier identifier, ui::ElementContext context, @@ -19,4 +22,11 @@ void TrackedElementWebContents::Init() { ui::ElementTracker::GetFrameworkDelegate()->NotifyElementShown(this); } +std::string TrackedElementWebContents::ToString() const { + auto result = TrackedElement::ToString(); + result.append(" with contents "); + result.append(owner_->web_contents()->GetURL().spec()); + return result; +} + DEFINE_FRAMEWORK_SPECIFIC_METADATA(TrackedElementWebContents) diff --git a/chrome/test/interaction/tracked_element_webcontents.h b/chrome/test/interaction/tracked_element_webcontents.h index 489f89883a36a9..7a557c04ff54c6 100644 --- a/chrome/test/interaction/tracked_element_webcontents.h +++ b/chrome/test/interaction/tracked_element_webcontents.h @@ -25,6 +25,8 @@ class TrackedElementWebContents : public ui::TrackedElement { DECLARE_FRAMEWORK_SPECIFIC_METADATA() + std::string ToString() const override; + WebContentsInteractionTestUtil* owner() { return owner_; } private: diff --git a/chrome/test/interaction/webcontents_interaction_test_util_interactive_uitest.cc b/chrome/test/interaction/webcontents_interaction_test_util_interactive_uitest.cc index 7509e6812e8793..3289a292751c7b 100644 --- a/chrome/test/interaction/webcontents_interaction_test_util_interactive_uitest.cc +++ b/chrome/test/interaction/webcontents_interaction_test_util_interactive_uitest.cc @@ -102,9 +102,12 @@ IN_PROC_BROWSER_TEST_F( .SetType(ui::InteractionSequence::StepType::kShown) .SetElementID(kAppMenuButtonElementId) .SetStartCallback(base::BindLambdaForTesting( - [&](ui::InteractionSequence*, + [&](ui::InteractionSequence* seq, ui::TrackedElement* element) { - test_util_.PressButton(element); + if (test_util_.PressButton(element) != + ui::test::ActionResult::kSucceeded) { + seq->FailForTesting(); + } })) .Build()) .AddStep(ui::InteractionSequence::StepBuilder() @@ -112,9 +115,12 @@ IN_PROC_BROWSER_TEST_F( .SetElementID(AppMenuModel::kDownloadsMenuItem) .SetMustRemainVisible(false) .SetStartCallback(base::BindLambdaForTesting( - [&](ui::InteractionSequence*, + [&](ui::InteractionSequence* seq, ui::TrackedElement* element) { - test_util_.SelectMenuItem(element); + if (test_util_.SelectMenuItem(element) != + ui::test::ActionResult::kSucceeded) { + seq->FailForTesting(); + } })) .Build()) .AddStep(ui::InteractionSequence::StepBuilder() @@ -183,9 +189,12 @@ IN_PROC_BROWSER_TEST_F(WebContentsInteractionTestUtilInteractiveUiTest, .SetType(ui::InteractionSequence::StepType::kShown) .SetElementID(kTabSearchButtonElementId) .SetStartCallback(base::BindLambdaForTesting( - [&](ui::InteractionSequence*, + [&](ui::InteractionSequence* seq, ui::TrackedElement* element) { - test_util_.PressButton(element); + if (test_util_.PressButton(element) != + ui::test::ActionResult::kSucceeded) { + seq->FailForTesting(); + } })) .Build()) .AddStep( @@ -253,9 +262,12 @@ IN_PROC_BROWSER_TEST_F(WebContentsInteractionTestUtilInteractiveUiTest, .SetType(ui::InteractionSequence::StepType::kShown) .SetElementID(kTabSearchButtonElementId) .SetStartCallback(base::BindLambdaForTesting( - [&](ui::InteractionSequence*, + [&](ui::InteractionSequence* seq, ui::TrackedElement* element) { - test_util_.PressButton(element); + if (test_util_.PressButton(element) != + ui::test::ActionResult::kSucceeded) { + seq->FailForTesting(); + } })) .Build()) .AddStep( @@ -337,9 +349,12 @@ IN_PROC_BROWSER_TEST_F(WebContentsInteractionTestUtilInteractiveUiTest, .SetType(ui::InteractionSequence::StepType::kShown) .SetElementID(kAppMenuButtonElementId) .SetStartCallback(base::BindLambdaForTesting( - [&](ui::InteractionSequence*, + [&](ui::InteractionSequence* seq, ui::TrackedElement* element) { - test_util_.PressButton(element); + if (test_util_.PressButton(element) != + ui::test::ActionResult::kSucceeded) { + seq->FailForTesting(); + } })) .Build()) .AddStep(ui::InteractionSequence::StepBuilder() @@ -347,9 +362,12 @@ IN_PROC_BROWSER_TEST_F(WebContentsInteractionTestUtilInteractiveUiTest, .SetElementID(AppMenuModel::kDownloadsMenuItem) .SetMustRemainVisible(false) .SetStartCallback(base::BindLambdaForTesting( - [&](ui::InteractionSequence*, + [&](ui::InteractionSequence* seq, ui::TrackedElement* element) { - test_util_.SelectMenuItem(element); + if (test_util_.SelectMenuItem(element) != + ui::test::ActionResult::kSucceeded) { + seq->FailForTesting(); + } })) .Build()) .AddStep(ui::InteractionSequence::StepBuilder() @@ -422,9 +440,12 @@ IN_PROC_BROWSER_TEST_F(WebContentsInteractionTestUtilInteractiveUiTest, .SetType(ui::InteractionSequence::StepType::kShown) .SetElementID(kAppMenuButtonElementId) .SetStartCallback(base::BindLambdaForTesting( - [&](ui::InteractionSequence*, + [&](ui::InteractionSequence* seq, ui::TrackedElement* element) { - test_util_.PressButton(element); + if (test_util_.PressButton(element) != + ui::test::ActionResult::kSucceeded) { + seq->FailForTesting(); + } })) .Build()) .AddStep(ui::InteractionSequence::StepBuilder() @@ -432,9 +453,12 @@ IN_PROC_BROWSER_TEST_F(WebContentsInteractionTestUtilInteractiveUiTest, .SetElementID(AppMenuModel::kDownloadsMenuItem) .SetMustRemainVisible(false) .SetStartCallback(base::BindLambdaForTesting( - [&](ui::InteractionSequence*, + [&](ui::InteractionSequence* seq, ui::TrackedElement* element) { - test_util_.SelectMenuItem(element); + if (test_util_.SelectMenuItem(element) != + ui::test::ActionResult::kSucceeded) { + seq->FailForTesting(); + } })) .Build()) .AddStep(ui::InteractionSequence::StepBuilder() diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn index 798c1d564b6ae5..acd06385277ac9 100644 --- a/ui/base/BUILD.gn +++ b/ui/base/BUILD.gn @@ -126,6 +126,7 @@ component("base") { "interaction/element_identifier.h", "interaction/element_tracker.cc", "interaction/element_tracker.h", + "interaction/framework_specific_implementation.cc", "interaction/framework_specific_implementation.h", "interaction/interaction_sequence.cc", "interaction/interaction_sequence.h", diff --git a/ui/base/interaction/element_test_util.cc b/ui/base/interaction/element_test_util.cc index 227e45bda67f8b..468af64da4e577 100644 --- a/ui/base/interaction/element_test_util.cc +++ b/ui/base/interaction/element_test_util.cc @@ -6,12 +6,10 @@ #include "base/test/bind.h" #include "ui/base/interaction/element_tracker.h" +#include "ui/base/interaction/framework_specific_implementation.h" namespace ui::test { -DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestFrameworkIdentifier); -DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kOtherFrameworkIdentifier); - TestElementBase::TestElementBase(ElementIdentifier id, ElementContext context) : TrackedElement(id, context) {} @@ -50,25 +48,7 @@ void TestElementBase::SendCustomEvent(CustomElementEventType event_type) { ElementTracker::GetFrameworkDelegate()->NotifyCustomEvent(this, event_type); } -// static -TrackedElement::FrameworkIdentifier TestElement::GetFrameworkIdentifier() { - return kTestFrameworkIdentifier; -} - -TrackedElement::FrameworkIdentifier -TestElement::GetInstanceFrameworkIdentifier() const { - return kTestFrameworkIdentifier; -} - -// static -TrackedElement::FrameworkIdentifier -TestElementOtherFramework::GetFrameworkIdentifier() { - return kOtherFrameworkIdentifier; -} - -TrackedElement::FrameworkIdentifier -TestElementOtherFramework::GetInstanceFrameworkIdentifier() const { - return kOtherFrameworkIdentifier; -} +DEFINE_FRAMEWORK_SPECIFIC_METADATA(TestElement) +DEFINE_FRAMEWORK_SPECIFIC_METADATA(TestElementOtherFramework) } // namespace ui::test diff --git a/ui/base/interaction/element_test_util.h b/ui/base/interaction/element_test_util.h index c7f5904caba8a4..4eb1b00fb060bc 100644 --- a/ui/base/interaction/element_test_util.h +++ b/ui/base/interaction/element_test_util.h @@ -7,6 +7,7 @@ #include "ui/base/interaction/element_identifier.h" #include "ui/base/interaction/element_tracker.h" +#include "ui/base/interaction/framework_specific_implementation.h" namespace ui::test { @@ -37,8 +38,7 @@ class TestElementBase : public TrackedElement { class TestElement : public TestElementBase { public: TestElement(ElementIdentifier id, ElementContext context); - static FrameworkIdentifier GetFrameworkIdentifier(); - FrameworkIdentifier GetInstanceFrameworkIdentifier() const override; + DECLARE_FRAMEWORK_SPECIFIC_METADATA() }; // Provides a platform-less test element in a fictional UI framework distinct @@ -46,8 +46,7 @@ class TestElement : public TestElementBase { class TestElementOtherFramework : public TestElementBase { public: TestElementOtherFramework(ElementIdentifier id, ElementContext context); - static FrameworkIdentifier GetFrameworkIdentifier(); - FrameworkIdentifier GetInstanceFrameworkIdentifier() const override; + DECLARE_FRAMEWORK_SPECIFIC_METADATA() }; // Convenience typedef for unique pointers to test elements. diff --git a/ui/base/interaction/element_tracker.cc b/ui/base/interaction/element_tracker.cc index 3896b6c9b57d04..00340d0b322b0e 100644 --- a/ui/base/interaction/element_tracker.cc +++ b/ui/base/interaction/element_tracker.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include "base/auto_reset.h" #include "base/bind.h" @@ -184,6 +185,13 @@ TrackedElement::TrackedElement(ElementIdentifier id, ElementContext context) TrackedElement::~TrackedElement() = default; +std::string TrackedElement::ToString() const { + std::ostringstream oss; + oss << GetImplementationName() << "(" << identifier() << ", " << context() + << ")"; + return oss.str(); +} + // static ElementTracker* ElementTracker::GetElementTracker() { static base::NoDestructor instance; diff --git a/ui/base/interaction/element_tracker.h b/ui/base/interaction/element_tracker.h index db21974156986a..61f41e12f847ed 100644 --- a/ui/base/interaction/element_tracker.h +++ b/ui/base/interaction/element_tracker.h @@ -51,6 +51,9 @@ class COMPONENT_EXPORT(UI_BASE) TrackedElement ElementIdentifier identifier() const { return identifier_; } ElementContext context() const { return context_; } + // FrameworkSpecificImplementation: + std::string ToString() const override; + protected: TrackedElement(ElementIdentifier identifier, ElementContext context); diff --git a/ui/base/interaction/framework_specific_implementation.cc b/ui/base/interaction/framework_specific_implementation.cc new file mode 100644 index 00000000000000..be595f07c08040 --- /dev/null +++ b/ui/base/interaction/framework_specific_implementation.cc @@ -0,0 +1,25 @@ +// 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/framework_specific_implementation.h" + +#include + +namespace ui { + +std::string FrameworkSpecificImplementation::ToString() const { + return GetImplementationName(); +} + +void PrintTo(const FrameworkSpecificImplementation& impl, std::ostream* os) { + *os << impl.ToString(); +} + +std::ostream& operator<<(std::ostream& os, + const FrameworkSpecificImplementation& impl) { + PrintTo(impl, &os); + return os; +} + +} // namespace ui diff --git a/ui/base/interaction/framework_specific_implementation.h b/ui/base/interaction/framework_specific_implementation.h index 845bd8a317aade..eb430df6664e4d 100644 --- a/ui/base/interaction/framework_specific_implementation.h +++ b/ui/base/interaction/framework_specific_implementation.h @@ -6,8 +6,11 @@ #define UI_BASE_INTERACTION_FRAMEWORK_SPECIFIC_IMPLEMENTATION_H_ #include +#include +#include #include "base/callback.h" +#include "base/component_export.h" #include "ui/base/interaction/element_identifier.h" namespace ui { @@ -77,6 +80,12 @@ class COMPONENT_EXPORT(UI_BASE) FrameworkSpecificImplementation { : nullptr; } + // Gets the class name of the implementation. + virtual const char* GetImplementationName() const = 0; + + // Gets a string representation of this element. + virtual std::string ToString() const; + protected: // Use DECLARE/DEFINE_FRAMEWORK_SPECIFIC_METADATA() - see below - to // implement this method in your framework-specific derived classes. @@ -86,9 +95,13 @@ class COMPONENT_EXPORT(UI_BASE) FrameworkSpecificImplementation { // These macros can be used to help define platform-specific subclasses of // base classes derived from FrameworkSpecificImplementation. #define DECLARE_FRAMEWORK_SPECIFIC_METADATA() \ + const char* GetImplementationName() const override; \ static FrameworkIdentifier GetFrameworkIdentifier(); \ FrameworkIdentifier GetInstanceFrameworkIdentifier() const override; #define DEFINE_FRAMEWORK_SPECIFIC_METADATA(ClassName) \ + const char* ClassName::GetImplementationName() const { \ + return #ClassName; \ + } \ ui::FrameworkSpecificImplementation::FrameworkIdentifier \ ClassName::GetFrameworkIdentifier() { \ DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(k##ClassName##Identifier); \ @@ -175,6 +188,14 @@ class FrameworkSpecificRegistrationList { ListType instances_; }; +COMPONENT_EXPORT(UI_BASE) +extern void PrintTo(const FrameworkSpecificImplementation& impl, + std::ostream* os); + +COMPONENT_EXPORT(UI_BASE) +extern std::ostream& operator<<(std::ostream& os, + const FrameworkSpecificImplementation& impl); + } // namespace ui #endif // UI_BASE_INTERACTION_FRAMEWORK_SPECIFIC_IMPLEMENTATION_H_ diff --git a/ui/base/interaction/interaction_test_util.cc b/ui/base/interaction/interaction_test_util.cc index 5b3602b23fff1c..a627cc4a84cde8 100644 --- a/ui/base/interaction/interaction_test_util.cc +++ b/ui/base/interaction/interaction_test_util.cc @@ -3,169 +3,151 @@ // found in the LICENSE file. #include "ui/base/interaction/interaction_test_util.h" +#include namespace ui::test { -bool InteractionTestUtil::Simulator::PressButton(TrackedElement*, InputType) { - return false; +namespace { + +template +ActionResult Simulate( + const std::vector>& + simulators, + ActionResult (InteractionTestUtil::Simulator::*method)(Args...), + Args... args) { + for (const auto& simulator : simulators) { + const auto result = std::invoke(method, simulator.get(), args...); + if (result != ActionResult::kNotAttempted) { + return result; + } + } + return ActionResult::kNotAttempted; +} + +} // namespace + +ActionResult InteractionTestUtil::Simulator::PressButton(TrackedElement*, + InputType) { + return ActionResult::kNotAttempted; } -bool InteractionTestUtil::Simulator::SelectMenuItem(TrackedElement*, - InputType) { - return false; +ActionResult InteractionTestUtil::Simulator::SelectMenuItem(TrackedElement*, + InputType) { + return ActionResult::kNotAttempted; } -bool InteractionTestUtil::Simulator::DoDefaultAction(TrackedElement*, - InputType) { - return false; +ActionResult InteractionTestUtil::Simulator::DoDefaultAction(TrackedElement*, + InputType) { + return ActionResult::kNotAttempted; } -bool InteractionTestUtil::Simulator::SelectTab(TrackedElement*, - size_t, - InputType) { - return false; +ActionResult InteractionTestUtil::Simulator::SelectTab(TrackedElement*, + size_t, + InputType) { + return ActionResult::kNotAttempted; } -bool InteractionTestUtil::Simulator::SelectDropdownItem(TrackedElement*, - size_t, - InputType) { - return false; +ActionResult InteractionTestUtil::Simulator::SelectDropdownItem(TrackedElement*, + size_t, + InputType) { + return ActionResult::kNotAttempted; } -bool InteractionTestUtil::Simulator::EnterText(TrackedElement* element, - const std::u16string& text, - TextEntryMode mode) { - return false; +ActionResult InteractionTestUtil::Simulator::EnterText(TrackedElement* element, + std::u16string text, + TextEntryMode mode) { + return ActionResult::kNotAttempted; } -bool InteractionTestUtil::Simulator::ActivateSurface(TrackedElement* element) { - return false; +ActionResult InteractionTestUtil::Simulator::ActivateSurface( + TrackedElement* element) { + return ActionResult::kNotAttempted; } #if !BUILDFLAG(IS_IOS) -bool InteractionTestUtil::Simulator::SendAccelerator( +ActionResult InteractionTestUtil::Simulator::SendAccelerator( TrackedElement* element, - const Accelerator& accelerator) { - return false; + Accelerator accelerator) { + return ActionResult::kNotAttempted; } #endif -bool InteractionTestUtil::Simulator::Confirm(TrackedElement* element) { - return false; +ActionResult InteractionTestUtil::Simulator::Confirm(TrackedElement* element) { + return ActionResult::kNotAttempted; } InteractionTestUtil::InteractionTestUtil() = default; InteractionTestUtil::~InteractionTestUtil() = default; -void InteractionTestUtil::PressButton(TrackedElement* element, - InputType input_type) { - for (const auto& simulator : simulators_) { - if (simulator->PressButton(element, input_type)) - return; - } - - // If a test has requested an invalid operation on an element, then this is - // an error. - NOTREACHED(); +ActionResult InteractionTestUtil::PressButton(TrackedElement* element, + InputType input_type) { + return Simulate(simulators_, &Simulator::PressButton, element, input_type); } -void InteractionTestUtil::SelectMenuItem(TrackedElement* element, - InputType input_type) { - for (const auto& simulator : simulators_) { - if (simulator->SelectMenuItem(element, input_type)) - return; - } - - // If a test has requested an invalid operation on an element, then this is - // an error. - NOTREACHED(); +ActionResult InteractionTestUtil::SelectMenuItem(TrackedElement* element, + InputType input_type) { + return Simulate(simulators_, &Simulator::SelectMenuItem, element, input_type); } -void InteractionTestUtil::DoDefaultAction(TrackedElement* element, - InputType input_type) { - for (const auto& simulator : simulators_) { - if (simulator->DoDefaultAction(element, input_type)) - return; - } - - // If a test has requested an invalid operation on an element, then this is - // an error. - NOTREACHED(); +ActionResult InteractionTestUtil::DoDefaultAction(TrackedElement* element, + InputType input_type) { + return Simulate(simulators_, &Simulator::DoDefaultAction, element, + input_type); } -void InteractionTestUtil::SelectTab(TrackedElement* tab_collection, - size_t index, - InputType input_type) { - for (const auto& simulator : simulators_) { - if (simulator->SelectTab(tab_collection, index, input_type)) - return; - } - - // If a test has requested an invalid operation on an element, then this is - // an error. - NOTREACHED(); +ActionResult InteractionTestUtil::SelectTab(TrackedElement* tab_collection, + size_t index, + InputType input_type) { + return Simulate(simulators_, &Simulator::SelectTab, tab_collection, index, + input_type); } -void InteractionTestUtil::SelectDropdownItem(TrackedElement* dropdown, - size_t index, - InputType input_type) { - for (const auto& simulator : simulators_) { - if (simulator->SelectDropdownItem(dropdown, index, input_type)) - return; - } - - // If a test has requested an invalid operation on an element, then this is - // an error. - NOTREACHED(); +ActionResult InteractionTestUtil::SelectDropdownItem(TrackedElement* dropdown, + size_t index, + InputType input_type) { + return Simulate(simulators_, &Simulator::SelectDropdownItem, dropdown, index, + input_type); } -void InteractionTestUtil::EnterText(TrackedElement* element, - std::u16string text, - TextEntryMode mode) { - for (const auto& simulator : simulators_) { - if (simulator->EnterText(element, text, mode)) - return; - } - - // If a test has requested an invalid operation on an element, then this is - // an error. - NOTREACHED(); +ActionResult InteractionTestUtil::EnterText(TrackedElement* element, + std::u16string text, + TextEntryMode mode) { + return Simulate(simulators_, &Simulator::EnterText, element, text, mode); } -void InteractionTestUtil::ActivateSurface(TrackedElement* element) { - for (const auto& simulator : simulators_) { - if (simulator->ActivateSurface(element)) - return; - } - - // If a test has requested an invalid operation on an element, then this is - // an error. - NOTREACHED(); +ActionResult InteractionTestUtil::ActivateSurface(TrackedElement* element) { + return Simulate(simulators_, &Simulator::ActivateSurface, element); } #if !BUILDFLAG(IS_IOS) -void InteractionTestUtil::SendAccelerator(TrackedElement* element, - Accelerator accelerator) { - for (const auto& simulator : simulators_) { - if (simulator->SendAccelerator(element, accelerator)) - return; - } - - // If a test has requested an invalid operation on an element, then this is - // an error. - NOTREACHED(); +ActionResult InteractionTestUtil::SendAccelerator(TrackedElement* element, + Accelerator accelerator) { + return Simulate(simulators_, &Simulator::SendAccelerator, element, + accelerator); } #endif -void InteractionTestUtil::Confirm(TrackedElement* element) { - for (const auto& simulator : simulators_) { - if (simulator->Confirm(element)) - return; - } +ActionResult InteractionTestUtil::Confirm(TrackedElement* element) { + return Simulate(simulators_, &Simulator::Confirm, element); +} + +void PrintTo(InteractionTestUtil::InputType input_type, std::ostream* os) { + const char* const kInputTypeNames[] = { + "InputType::kDontCare", "InputType::kMouse", "InputType::kKeyboard", + "InputType::kMouse"}; + constexpr int kCount = sizeof(kInputTypeNames) / sizeof(kInputTypeNames[0]); + static_assert(kCount == + static_cast(InteractionTestUtil::InputType::kMaxValue) + + 1); + const int value = static_cast(input_type); + *os << ((value < 0 || value >= kCount) ? "[invalid InputType]" + : kInputTypeNames[value]); +} - // If a test has requested an invalid operation on an element, then this is - // an error. - NOTREACHED(); +std::ostream& operator<<(std::ostream& os, + InteractionTestUtil::InputType input_type) { + PrintTo(input_type, &os); + return os; } } // namespace ui::test \ No newline at end of file diff --git a/ui/base/interaction/interaction_test_util.h b/ui/base/interaction/interaction_test_util.h index b20166c5f0fde0..baf6510e098c0a 100644 --- a/ui/base/interaction/interaction_test_util.h +++ b/ui/base/interaction/interaction_test_util.h @@ -17,6 +17,37 @@ namespace ui::test { +// Describes the result of a trying to perform a specific action as part of a +// test. Returned by individual functions and action simulators (see +// `InteractionTestUtil::Simulator` below). +enum class [[nodiscard]] ActionResult { + // Indicates that the code did not know how to perform the action on the + // requested target. In the case of an action simulator, other simulators + // should be tried instead. Otherwise, treat as failure. + kNotAttempted, + + // Indicates that the action succeeded. + kSucceeded, + + // Indicates that the code *does* know how to perform the action on the + // requested target, attempted to do so, and failed. No further attempts at + // performing the action should be made. + kFailed, + + // Indicates that the code *does* know how to perform the action, but + // recognized that it would not succeed DUE TO A KNOWN ISSUE OR + // INCOMPATIBILITY in the current platform, build, or job environment. + // + // An action that fails unexpectedly should always return kFailed instead. + // + // Code that returns this value should log or document the exact circumstances + // that lead to the known incompatibility. + // + // No further attempts at performing the action should be made. Should be + // treated as failure by default. + kKnownIncompatible +}; + // Platform- and framework-independent utility for delegating specific common // actions to framework-specific handlers. Use so you can write your // interaction tests without having to worry about framework specifics. @@ -44,7 +75,11 @@ namespace ui::test { // ... // step.SetStartCallback(base::BindLambdaForTesting([&] ( // InteractionSequence* seq, TrackedElement* element) { -// test_util_.PressButton(element); +// ActionResult result = test_util_.PressButton(element); +// if (result == ActionResult::kError) +// seq->FailForTesting(); +// else if (result = ActionResult::kNotSupportedOnThisPlatform) +// seq->SkipForTesting(); // })) // ... // } @@ -68,7 +103,9 @@ class InteractionTestUtil { // Simulate the input explicitly via kayboard events. kKeyboard, // Simulate the input explicitly via touch events. - kTouch + kTouch, + // If values are added to the enumeration, update this value. + kMaxValue = kTouch }; // How should text be sent to a text input? @@ -99,49 +136,49 @@ class InteractionTestUtil { // Tries to press `element` as if it is a button. Returns false if `element` // is an unsupported type or if `input_type` is not supported. - [[nodiscard]] virtual bool PressButton(TrackedElement* element, - InputType input_type); + virtual ActionResult PressButton(TrackedElement* element, + InputType input_type); // Tries to select `element` as if it is a menu item. Returns false if // `element` is an unsupported type or if `input_type` is not supported. - [[nodiscard]] virtual bool SelectMenuItem(TrackedElement* element, - InputType input_type); + virtual ActionResult SelectMenuItem(TrackedElement* element, + InputType input_type); // Triggers the default action of the target element, which is typically // whatever happens when the user clicks/taps it. If `element` is a button // or menu item, prefer PressButton() or SelectMenuItem() instead. - [[nodiscard]] virtual bool DoDefaultAction(TrackedElement* element, - InputType input_type); + virtual ActionResult DoDefaultAction(TrackedElement* element, + InputType input_type); // Tries to select tab `index` in `tab_collection`. The collection could be // a tabbed pane, browser/tabstrip, or similar. Note that `index` is // zero-indexed. - [[nodiscard]] virtual bool SelectTab(TrackedElement* tab_collection, - size_t index, - InputType input_type); + virtual ActionResult SelectTab(TrackedElement* tab_collection, + size_t index, + InputType input_type); // Tries to select item `index` in `dropdown`. The collection could be // a listbox, combobox, or similar. Note that `index` is zero-indexed. - [[nodiscard]] virtual bool SelectDropdownItem(TrackedElement* dropdown, - size_t index, - InputType input_type); + virtual ActionResult SelectDropdownItem(TrackedElement* dropdown, + size_t index, + InputType input_type); // Sets or modifies the text of a text box, editable combobox, etc. - [[nodiscard]] virtual bool EnterText(TrackedElement* element, - const std::u16string& text, - TextEntryMode mode); + virtual ActionResult EnterText(TrackedElement* element, + std::u16string text, + TextEntryMode mode); // Activates the surface containing `element`. - [[nodiscard]] virtual bool ActivateSurface(TrackedElement* element); + virtual ActionResult ActivateSurface(TrackedElement* element); #if !BUILDFLAG(IS_IOS) // Sends the given accelerator to the surface containing the element. - [[nodiscard]] virtual bool SendAccelerator(TrackedElement* element, - const Accelerator& accelerator); + virtual ActionResult SendAccelerator(TrackedElement* element, + Accelerator accelerator); #endif // Sends a "confirm" input to `element`, e.g. a RETURN keypress. - [[nodiscard]] virtual bool Confirm(TrackedElement* element); + virtual ActionResult Confirm(TrackedElement* element); }; InteractionTestUtil(); @@ -159,20 +196,20 @@ class InteractionTestUtil { // Simulate a button press on `element`. Will fail if `element` is not a // button or if `input_type` is not supported. - void PressButton(TrackedElement* element, - InputType input_type = InputType::kDontCare); + ActionResult PressButton(TrackedElement* element, + InputType input_type = InputType::kDontCare); // Simulate the menu item `element` being selected by the user. Will fail if // `element` is not a menu item or if `input_type` is not supported. - void SelectMenuItem(TrackedElement* element, - InputType input_type = InputType::kDontCare); + ActionResult SelectMenuItem(TrackedElement* element, + InputType input_type = InputType::kDontCare); // Simulate selecting the `index`-th tab (zero-indexed) of `tab_collection`. // Will fail if the target object is not a supported type, if `index` is out // of bounds, or if `input_type` is not supported. - void SelectTab(TrackedElement* tab_collection, - size_t index, - InputType input_type = InputType::kDontCare); + ActionResult SelectTab(TrackedElement* tab_collection, + size_t index, + InputType input_type = InputType::kDontCare); // Simulate selecting item `index` in `dropdown`. The collection could be // a listbox, combobox, or similar. Will fail if the target object is not a @@ -185,42 +222,48 @@ class InteractionTestUtil { // implementation across platforms and can be a source of flakiness. Options // other than kDontCare may not be supported on all platforms for this reason; // if they are not, an error message will be printed and the test will fail. - void SelectDropdownItem(TrackedElement* dropdown, - size_t index, - InputType input_type = InputType::kDontCare); + ActionResult SelectDropdownItem(TrackedElement* dropdown, + size_t index, + InputType input_type = InputType::kDontCare); // Simulate the default action for `element` - typically whatever happens when // the user clicks or taps on it. Will fail if `input_type` is not supported. // Prefer PressButton() for buttons and SelectMenuItem() for menu items. - void DoDefaultAction(TrackedElement* element, - InputType input_type = InputType::kDontCare); + ActionResult DoDefaultAction(TrackedElement* element, + InputType input_type = InputType::kDontCare); // Sets or modifies the text of a text box, editable combobox, etc. `text` is // the text to enter, and `mode` specifies how it should be entered. Default // is replace existing text. - void EnterText(TrackedElement* element, - std::u16string text, - TextEntryMode mode = TextEntryMode::kReplaceAll); + ActionResult EnterText(TrackedElement* element, + std::u16string text, + TextEntryMode mode = TextEntryMode::kReplaceAll); // Activates the surface containing `element`. - void ActivateSurface(TrackedElement* element); + ActionResult ActivateSurface(TrackedElement* element); #if !BUILDFLAG(IS_IOS) // Sends `accelerator` to the surface containing `element`. May not work if // the surface is not active. Prefer to use only in single-process test // fixtures like interactive_ui_tests, especially for app/browser // accelerators. - void SendAccelerator(TrackedElement* element, Accelerator accelerator); + ActionResult SendAccelerator(TrackedElement* element, + Accelerator accelerator); #endif // Sends a "confirm" input to `element`, e.g. a RETURN keypress. - void Confirm(TrackedElement* element); + ActionResult Confirm(TrackedElement* element); private: // The list of known simulators. std::vector> simulators_; }; +void PrintTo(InteractionTestUtil::InputType input_type, std::ostream* os); + +std::ostream& operator<<(std::ostream& os, + InteractionTestUtil::InputType input_type); + } // namespace ui::test #endif // UI_BASE_INTERACTION_INTERACTION_TEST_UTIL_H_ diff --git a/ui/base/interaction/interaction_test_util_mac.h b/ui/base/interaction/interaction_test_util_mac.h index 492b6bae98f0d8..59e5f3654627cc 100644 --- a/ui/base/interaction/interaction_test_util_mac.h +++ b/ui/base/interaction/interaction_test_util_mac.h @@ -21,8 +21,8 @@ class InteractionTestUtilSimulatorMac : public InteractionTestUtil::Simulator { ~InteractionTestUtilSimulatorMac() override; // InteractionTestUtil::Simulator: - bool SelectMenuItem(ui::TrackedElement* element, - InputType input_type) override; + ActionResult SelectMenuItem(ui::TrackedElement* element, + InputType input_type) override; }; } // namespace ui::test diff --git a/ui/base/interaction/interaction_test_util_mac.mm b/ui/base/interaction/interaction_test_util_mac.mm index ca511a4e71a415..1ed93a270946dd 100644 --- a/ui/base/interaction/interaction_test_util_mac.mm +++ b/ui/base/interaction/interaction_test_util_mac.mm @@ -14,29 +14,35 @@ InteractionTestUtilSimulatorMac::InteractionTestUtilSimulatorMac() = default; InteractionTestUtilSimulatorMac::~InteractionTestUtilSimulatorMac() = default; -bool InteractionTestUtilSimulatorMac::SelectMenuItem( +ActionResult InteractionTestUtilSimulatorMac::SelectMenuItem( ui::TrackedElement* element, InputType input_type) { auto* const mac_element = element->AsA(); if (!mac_element) - return false; - - // We can't inject specific inputs; so only "don't care" is supported through - // direct programmatic simulation of menu engagement. - if (input_type != InputType::kDontCare) - return false; + return ActionResult::kNotAttempted; NSMenu* menu = ElementTrackerMac::GetInstance()->GetRootMenuForContext( mac_element->context()); if (!menu) - return false; + return ActionResult::kNotAttempted; + + if (input_type != InputType::kDontCare) { + LOG(WARNING) << "SelectMenuItem on Mac does not support specific input " + "types; use InputType::kDontCare"; + return ActionResult::kKnownIncompatible; + } MenuControllerCocoa* controller = base::mac::ObjCCastStrict([menu delegate]); - DCHECK(controller); + if (!controller) { + LOG(ERROR) << "Cannot retrieve MenuControllerCocoa from menu."; + return ActionResult::kFailed; + } ui::MenuModel* const model = [controller model]; - if (!model) - return false; + if (!model) { + LOG(ERROR) << "Cannot retrieve MenuModel from controller."; + return ActionResult::kFailed; + } for (size_t i = 0; i < model->GetItemCount(); ++i) { if (model->GetElementIdentifierAt(i) == element->identifier()) { @@ -45,12 +51,14 @@ DCHECK([item action]); [controller performSelector:[item action] withObject:item]; [controller cancel]; - return true; + return ActionResult::kSucceeded; } } } - return false; + LOG(ERROR) << "Item with id " << element->identifier() + << " not found in menu."; + return ActionResult::kFailed; } } // namespace ui::test diff --git a/ui/base/interaction/interaction_test_util_unittest.cc b/ui/base/interaction/interaction_test_util_unittest.cc index 6bb9526a8aae7f..94ce82f5c8850e 100644 --- a/ui/base/interaction/interaction_test_util_unittest.cc +++ b/ui/base/interaction/interaction_test_util_unittest.cc @@ -30,29 +30,29 @@ class MockInteractionSimulator : public InteractionTestUtil::Simulator { ~MockInteractionSimulator() override = default; MOCK_METHOD2(PressButton, - bool(TrackedElement* element, InputType input_type)); + ActionResult(TrackedElement* element, InputType input_type)); MOCK_METHOD2(SelectMenuItem, - bool(TrackedElement* element, InputType input_type)); + ActionResult(TrackedElement* element, InputType input_type)); MOCK_METHOD2(DoDefaultAction, - bool(TrackedElement* element, InputType input_type)); + ActionResult(TrackedElement* element, InputType input_type)); MOCK_METHOD3(SelectTab, - bool(TrackedElement* tab_collection, - size_t index, - InputType input_type)); + ActionResult(TrackedElement* tab_collection, + size_t index, + InputType input_type)); MOCK_METHOD3(SelectDropdownItem, - bool(TrackedElement* dropdown, - size_t index, - InputType input_type)); + ActionResult(TrackedElement* dropdown, + size_t index, + InputType input_type)); MOCK_METHOD3(EnterText, - bool(TrackedElement* element, - const std::u16string& text, - TextEntryMode mode)); - MOCK_METHOD1(ActivateSurface, bool(TrackedElement* element)); + ActionResult(TrackedElement* element, + std::u16string text, + TextEntryMode mode)); + MOCK_METHOD1(ActivateSurface, ActionResult(TrackedElement* element)); #if !BUILDFLAG(IS_IOS) MOCK_METHOD2(SendAccelerator, - bool(TrackedElement* element, const Accelerator& accelerator)); + ActionResult(TrackedElement* element, Accelerator accelerator)); #endif - MOCK_METHOD1(Confirm, bool(TrackedElement* element)); + MOCK_METHOD1(Confirm, ActionResult(TrackedElement* element)); }; } // namespace @@ -64,8 +64,8 @@ TEST(InteractionTestUtilTest, PressButton) { std::make_unique>()); EXPECT_CALL(*mock, PressButton(&element, InteractionTestUtil::InputType::kDontCare)) - .WillOnce(testing::Return(true)); - util.PressButton(&element); + .WillOnce(testing::Return(ActionResult::kSucceeded)); + EXPECT_EQ(ActionResult::kSucceeded, util.PressButton(&element)); } TEST(InteractionTestUtilTest, SelectMenuItem) { @@ -75,8 +75,8 @@ TEST(InteractionTestUtilTest, SelectMenuItem) { std::make_unique>()); EXPECT_CALL(*mock, SelectMenuItem(&element, InteractionTestUtil::InputType::kDontCare)) - .WillOnce(testing::Return(true)); - util.SelectMenuItem(&element); + .WillOnce(testing::Return(ActionResult::kSucceeded)); + EXPECT_EQ(ActionResult::kSucceeded, util.SelectMenuItem(&element)); } TEST(InteractionTestUtilTest, DoDefaultAction) { @@ -86,8 +86,8 @@ TEST(InteractionTestUtilTest, DoDefaultAction) { std::make_unique>()); EXPECT_CALL(*mock, DoDefaultAction(&element, InteractionTestUtil::InputType::kDontCare)) - .WillOnce(testing::Return(true)); - util.DoDefaultAction(&element); + .WillOnce(testing::Return(ActionResult::kSucceeded)); + EXPECT_EQ(ActionResult::kSucceeded, util.DoDefaultAction(&element)); } TEST(InteractionTestUtilTest, SelectTab) { @@ -97,8 +97,8 @@ TEST(InteractionTestUtilTest, SelectTab) { std::make_unique>()); EXPECT_CALL( *mock, SelectTab(&element, 1U, InteractionTestUtil::InputType::kDontCare)) - .WillOnce(testing::Return(true)); - util.SelectTab(&element, 1U); + .WillOnce(testing::Return(ActionResult::kSucceeded)); + EXPECT_EQ(ActionResult::kSucceeded, util.SelectTab(&element, 1U)); } TEST(InteractionTestUtilTest, SelectDropdownItem) { @@ -109,8 +109,8 @@ TEST(InteractionTestUtilTest, SelectDropdownItem) { EXPECT_CALL(*mock, SelectDropdownItem(&element, 1U, InteractionTestUtil::InputType::kDontCare)) - .WillOnce(testing::Return(true)); - util.SelectDropdownItem(&element, 1U); + .WillOnce(testing::Return(ActionResult::kSucceeded)); + EXPECT_EQ(ActionResult::kSucceeded, util.SelectDropdownItem(&element, 1U)); } TEST(InteractionTestUtilTest, EnterText) { @@ -122,14 +122,17 @@ TEST(InteractionTestUtilTest, EnterText) { EXPECT_CALL(*mock, EnterText(&element, std::u16string(kText), InteractionTestUtil::TextEntryMode::kAppend)) - .WillOnce(testing::Return(true)); - util.EnterText(&element, kText, InteractionTestUtil::TextEntryMode::kAppend); + .WillOnce(testing::Return(ActionResult::kSucceeded)); + EXPECT_EQ(ActionResult::kSucceeded, + util.EnterText(&element, kText, + InteractionTestUtil::TextEntryMode::kAppend)); EXPECT_CALL(*mock, EnterText(&element, std::u16string(kText), InteractionTestUtil::TextEntryMode::kReplaceAll)) - .WillOnce(testing::Return(true)); - util.EnterText(&element, kText, - InteractionTestUtil::TextEntryMode::kReplaceAll); + .WillOnce(testing::Return(ActionResult::kSucceeded)); + EXPECT_EQ(ActionResult::kSucceeded, + util.EnterText(&element, kText, + InteractionTestUtil::TextEntryMode::kReplaceAll)); } TEST(InteractionTestUtilTest, ActivateSurface) { @@ -138,8 +141,9 @@ TEST(InteractionTestUtilTest, ActivateSurface) { auto* const mock = util.AddSimulator( std::make_unique>()); - EXPECT_CALL(*mock, ActivateSurface(&element)).WillOnce(testing::Return(true)); - util.ActivateSurface(&element); + EXPECT_CALL(*mock, ActivateSurface(&element)) + .WillOnce(testing::Return(ActionResult::kSucceeded)); + EXPECT_EQ(ActionResult::kSucceeded, util.ActivateSurface(&element)); } #if !BUILDFLAG(IS_IOS) @@ -151,8 +155,8 @@ TEST(InteractionTestUtilTest, SendAccelerator) { Accelerator accel(KeyboardCode::VKEY_F5, EF_SHIFT_DOWN); EXPECT_CALL(*mock, SendAccelerator(&element, testing::Eq(accel))) - .WillOnce(testing::Return(true)); - util.SendAccelerator(&element, accel); + .WillOnce(testing::Return(ActionResult::kSucceeded)); + EXPECT_EQ(ActionResult::kSucceeded, util.SendAccelerator(&element, accel)); } #endif // !BUILDFLAG(IS_IOS) @@ -162,8 +166,96 @@ TEST(InteractionTestUtilTest, Confirm) { auto* const mock = util.AddSimulator( std::make_unique>()); - EXPECT_CALL(*mock, Confirm(&element)).WillOnce(testing::Return(true)); - util.Confirm(&element); + EXPECT_CALL(*mock, Confirm(&element)) + .WillOnce(testing::Return(ActionResult::kSucceeded)); + EXPECT_EQ(ActionResult::kSucceeded, util.Confirm(&element)); +} + +TEST(InteractionTestUtilTest, TwoSimulators_FirstSucceeds) { + TestElement element(kTestElementIdentifier, kTestElementContext); + InteractionTestUtil util; + auto* const sim1 = util.AddSimulator( + std::make_unique>()); + util.AddSimulator( + std::make_unique>()); + + EXPECT_CALL(*sim1, Confirm(&element)) + .WillOnce(testing::Return(ActionResult::kSucceeded)); + EXPECT_EQ(ActionResult::kSucceeded, util.Confirm(&element)); +} + +TEST(InteractionTestUtilTest, TwoSimulators_SecondSucceeds) { + TestElement element(kTestElementIdentifier, kTestElementContext); + InteractionTestUtil util; + auto* const sim1 = util.AddSimulator( + std::make_unique>()); + auto* const sim2 = util.AddSimulator( + std::make_unique>()); + + EXPECT_CALL(*sim1, Confirm(&element)) + .WillOnce(testing::Return(ActionResult::kNotAttempted)); + EXPECT_CALL(*sim2, Confirm(&element)) + .WillOnce(testing::Return(ActionResult::kSucceeded)); + + EXPECT_EQ(ActionResult::kSucceeded, util.Confirm(&element)); +} + +TEST(InteractionTestUtilTest, TwoSimulators_FirstFails) { + TestElement element(kTestElementIdentifier, kTestElementContext); + InteractionTestUtil util; + auto* const sim1 = util.AddSimulator( + std::make_unique>()); + util.AddSimulator( + std::make_unique>()); + + EXPECT_CALL(*sim1, Confirm(&element)) + .WillOnce(testing::Return(ActionResult::kFailed)); + EXPECT_EQ(ActionResult::kFailed, util.Confirm(&element)); +} + +TEST(InteractionTestUtilTest, TwoSimulators_SecondFails) { + TestElement element(kTestElementIdentifier, kTestElementContext); + InteractionTestUtil util; + auto* const sim1 = util.AddSimulator( + std::make_unique>()); + auto* const sim2 = util.AddSimulator( + std::make_unique>()); + + EXPECT_CALL(*sim1, Confirm(&element)) + .WillOnce(testing::Return(ActionResult::kNotAttempted)); + EXPECT_CALL(*sim2, Confirm(&element)) + .WillOnce(testing::Return(ActionResult::kFailed)); + + EXPECT_EQ(ActionResult::kFailed, util.Confirm(&element)); +} + +TEST(InteractionTestUtilTest, TwoSimulators_FirstUnsupported) { + TestElement element(kTestElementIdentifier, kTestElementContext); + InteractionTestUtil util; + auto* const sim1 = util.AddSimulator( + std::make_unique>()); + util.AddSimulator( + std::make_unique>()); + + EXPECT_CALL(*sim1, Confirm(&element)) + .WillOnce(testing::Return(ActionResult::kKnownIncompatible)); + EXPECT_EQ(ActionResult::kKnownIncompatible, util.Confirm(&element)); +} + +TEST(InteractionTestUtilTest, TwoSimulators_SecondUnsupported) { + TestElement element(kTestElementIdentifier, kTestElementContext); + InteractionTestUtil util; + auto* const sim1 = util.AddSimulator( + std::make_unique>()); + auto* const sim2 = util.AddSimulator( + std::make_unique>()); + + EXPECT_CALL(*sim1, Confirm(&element)) + .WillOnce(testing::Return(ActionResult::kNotAttempted)); + EXPECT_CALL(*sim2, Confirm(&element)) + .WillOnce(testing::Return(ActionResult::kKnownIncompatible)); + + EXPECT_EQ(ActionResult::kKnownIncompatible, util.Confirm(&element)); } } // namespace ui::test diff --git a/ui/base/interaction/interactive_test.cc b/ui/base/interaction/interactive_test.cc index 7cb9ebf71c9546..30ded3c25745f2 100644 --- a/ui/base/interaction/interactive_test.cc +++ b/ui/base/interaction/interactive_test.cc @@ -35,8 +35,12 @@ InteractionSequence::StepBuilder InteractiveTestApi::PressButton( internal::SpecifyElement(builder, button); builder.SetMustRemainVisible(false); builder.SetStartCallback(base::BindOnce( - [](InputType input_type, InteractiveTestApi* test, InteractionSequence*, - TrackedElement* el) { test->test_util().PressButton(el, input_type); }, + [](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; } @@ -49,9 +53,11 @@ InteractionSequence::StepBuilder InteractiveTestApi::SelectMenuItem( internal::SpecifyElement(builder, menu_item); builder.SetMustRemainVisible(false); builder.SetStartCallback(base::BindOnce( - [](InputType input_type, InteractiveTestApi* test, InteractionSequence*, - TrackedElement* el) { - test->test_util().SelectMenuItem(el, input_type); + [](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; @@ -65,9 +71,11 @@ InteractionSequence::StepBuilder InteractiveTestApi::DoDefaultAction( internal::SpecifyElement(builder, element); builder.SetMustRemainVisible(false); builder.SetStartCallback(base::BindOnce( - [](InputType input_type, InteractiveTestApi* test, InteractionSequence*, - TrackedElement* el) { - test->test_util().DoDefaultAction(el, input_type); + [](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; @@ -82,8 +90,10 @@ InteractionSequence::StepBuilder InteractiveTestApi::SelectTab( internal::SpecifyElement(builder, tab_collection); builder.SetStartCallback(base::BindOnce( [](size_t index, InputType input_type, InteractiveTestApi* test, - InteractionSequence*, TrackedElement* el) { - test->test_util().SelectTab(el, index, input_type); + 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; @@ -98,8 +108,10 @@ InteractionSequence::StepBuilder InteractiveTestApi::SelectDropdownItem( internal::SpecifyElement(builder, collection); builder.SetStartCallback(base::BindOnce( [](size_t item, InputType input_type, InteractiveTestApi* test, - InteractionSequence*, TrackedElement* el) { - test->test_util().SelectDropdownItem(el, item, input_type); + 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; @@ -115,8 +127,10 @@ InteractionSequence::StepBuilder InteractiveTestApi::EnterText( internal::SpecifyElement(builder, element); builder.SetStartCallback(base::BindOnce( [](std::u16string text, TextEntryMode mode, InteractiveTestApi* test, - InteractionSequence*, TrackedElement* el) { - test->test_util().EnterText(el, std::move(text), mode); + 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; @@ -128,8 +142,10 @@ InteractionSequence::StepBuilder InteractiveTestApi::ActivateSurface( builder.SetDescription("ActivateSurface()"); internal::SpecifyElement(builder, element); builder.SetStartCallback(base::BindOnce( - [](InteractiveTestApi* test, InteractionSequence*, TrackedElement* el) { - test->test_util().ActivateSurface(el); + [](InteractiveTestApi* test, InteractionSequence* seq, + TrackedElement* el) { + test->private_test_impl().HandleActionResult( + seq, el, "ActivateSurface", test->test_util().ActivateSurface(el)); }, base::Unretained(this))); return builder; @@ -146,8 +162,10 @@ InteractionSequence::StepBuilder InteractiveTestApi::SendAccelerator( internal::SpecifyElement(builder, element); builder.SetStartCallback(base::BindOnce( [](Accelerator accelerator, InteractiveTestApi* test, - InteractionSequence*, TrackedElement* el) { - test->test_util().SendAccelerator(el, accelerator); + InteractionSequence* seq, TrackedElement* el) { + test->private_test_impl().HandleActionResult( + seq, el, "SendAccelerator", + test->test_util().SendAccelerator(el, accelerator)); }, accelerator, base::Unretained(this))); return builder; @@ -159,10 +177,13 @@ InteractionSequence::StepBuilder InteractiveTestApi::Confirm( StepBuilder builder; builder.SetDescription("Confirm()"); internal::SpecifyElement(builder, element); - builder.SetStartCallback( - base::BindOnce([](InteractiveTestApi* test, InteractionSequence*, - TrackedElement* el) { test->test_util().Confirm(el); }, - base::Unretained(this))); + 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; } @@ -328,6 +349,19 @@ InteractiveTestApi::MultiStep InteractiveTestApi::InContext( 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) { diff --git a/ui/base/interaction/interactive_test.h b/ui/base/interaction/interactive_test.h index 195d3142125e38..3c234ba4ec5944 100644 --- a/ui/base/interaction/interactive_test.h +++ b/ui/base/interaction/interactive_test.h @@ -58,6 +58,8 @@ class InteractiveTestApi { 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 @@ -245,6 +247,15 @@ class InteractiveTestApi { template [[nodiscard]] StepBuilder InContext(ElementContext context, T&& step); + // 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_; diff --git a/ui/base/interaction/interactive_test_internal.cc b/ui/base/interaction/interactive_test_internal.cc index 0a3ca7d3388843..cbd3aab1f205db 100644 --- a/ui/base/interaction/interactive_test_internal.cc +++ b/ui/base/interaction/interactive_test_internal.cc @@ -26,6 +26,7 @@ InteractiveTestPrivate::~InteractiveTestPrivate() = default; void InteractiveTestPrivate::Init(ElementContext initial_context) { success_ = false; + sequence_skipped_ = false; MaybeAddPivotElement(initial_context); for (ElementContext context : ElementTracker::GetElementTracker()->GetAllContextsForTesting()) { @@ -58,6 +59,49 @@ void InteractiveTestPrivate::MaybeAddPivotElement(ElementContext context) { } } +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); @@ -86,10 +130,21 @@ void InteractiveTestPrivate::OnSequenceAborted( description); return; } - GTEST_FAIL() << "Interactive test failed on step " << active_step - << " for reason " << aborted_reason << ". Step type was " - << last_step_type << " with element " << last_id - << " description: " << description; + if (sequence_skipped_) { + LOG(WARNING) << "Interactive test halted on step " << active_step + << ". Step type was " << last_step_type << " with element " + << last_id << " description: " << description; + if (on_incompatible_action_ == OnIncompatibleAction::kSkipTest) { + GTEST_SKIP(); + } else { + DCHECK_EQ(OnIncompatibleAction::kHaltTest, on_incompatible_action_); + } + } else { + GTEST_FAIL() << "Interactive test failed on step " << active_step + << " for reason " << aborted_reason << ". Step type was " + << last_step_type << " with element " << last_id + << " description: " << description; + } } void SpecifyElement(ui::InteractionSequence::StepBuilder& builder, diff --git a/ui/base/interaction/interactive_test_internal.h b/ui/base/interaction/interactive_test_internal.h index eb5dbf207fcc47..0a351f9f1003ff 100644 --- a/ui/base/interaction/interactive_test_internal.h +++ b/ui/base/interaction/interactive_test_internal.h @@ -21,7 +21,6 @@ #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" namespace ui::test { @@ -40,6 +39,35 @@ class InteractiveTestPrivate { public: using MultiStep = std::vector; + // Describes what should happen when an action isn't compatible with the + // current build, platform, or environment. For example, not all tests are set + // up to handle screenshots, and some Linux window managers cannot bring a + // background window to the front. + // + // See chrome/test/interaction/README.md for best practices. + enum class OnIncompatibleAction { + // The test should fail. This is the default, and should be used in almost + // all cases. + kFailTest, + // The sequence should abort immediately and the test should be skipped. + // Use this when the remainder of the test would depend on the result of the + // incompatible step. Good for smoke/regression tests that have known + // incompatibilities but still need to be run in as many environments as + // possible. + kSkipTest, + // As `kSkipTest`, but instead of marking the test as skipped, just stops + // the test sequence. This is useful when the test cannot continue past the + // problematic step, but you also want to preserve any non-fatal errors that + // may have occurred up to that point (or check any conditions after the + // test stops). + kHaltTest, + // The failure should be ignored and the test should continue. + // Use this when the step does not affect the outcome of the test, such as + // taking an incidental screenshot in a test job that doesn't support + // screenshots. + kIgnoreAndContinue, + }; + explicit InteractiveTestPrivate( std::unique_ptr test_util); virtual ~InteractiveTestPrivate(); @@ -48,6 +76,19 @@ class InteractiveTestPrivate { InteractionTestUtil& test_util() { return *test_util_; } + OnIncompatibleAction on_incompatible_action() const { + return on_incompatible_action_; + } + + bool sequence_skipped() const { return sequence_skipped_; } + + // Possibly fails or skips a sequence based on the result of an action + // simulation. + void HandleActionResult(InteractionSequence* seq, + const TrackedElement* el, + const std::string& operation_name, + ActionResult result); + // Gets the pivot element for the specified context, which must exist. TrackedElement* GetPivotElement(ElementContext context) const; @@ -101,6 +142,15 @@ class InteractiveTestPrivate { // Tracks whether a sequence succeeded or failed. bool success_ = false; + // Specifies how an incompatible action should be handled. + OnIncompatibleAction on_incompatible_action_ = + OnIncompatibleAction::kFailTest; + std::string on_incompatible_action_reason_; + + // Tracks whether a sequence is skipped. Will only be set if + // `skip_on_unsupported_operation` is true. + bool sequence_skipped_ = false; + // Used to simulate input to UI elements. std::unique_ptr test_util_; diff --git a/ui/base/interaction/interactive_test_unittest.cc b/ui/base/interaction/interactive_test_unittest.cc index 5c231d400cc1d3..b255a4b087542b 100644 --- a/ui/base/interaction/interactive_test_unittest.cc +++ b/ui/base/interaction/interactive_test_unittest.cc @@ -16,6 +16,7 @@ #include "ui/base/interaction/element_test_util.h" #include "ui/base/interaction/element_tracker.h" #include "ui/base/interaction/expect_call_in_scope.h" +#include "ui/base/interaction/interaction_sequence.h" #if !BUILDFLAG(IS_IOS) #include "ui/base/accelerators/accelerator.h" @@ -52,63 +53,71 @@ DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kTestId4); DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kTestEvent1); DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kTestEvent2); +constexpr char kSetOnIncompatibleActionMessage[] = + "Explicitly testing incompatibility-handling."; + class TestSimulator : public InteractionTestUtil::Simulator { public: TestSimulator() = default; ~TestSimulator() override = default; - bool PressButton(TrackedElement* element, InputType input_type) override { + void set_result(ActionResult result) { result_ = result; } + + ActionResult PressButton(TrackedElement* element, + InputType input_type) override { DoAction(ActionType::kPressButton, element, input_type); - return true; + return result_; } - bool SelectMenuItem(TrackedElement* element, InputType input_type) override { + ActionResult SelectMenuItem(TrackedElement* element, + InputType input_type) override { DoAction(ActionType::kSelectMenuItem, element, input_type); - return true; + return result_; } - bool DoDefaultAction(TrackedElement* element, InputType input_type) override { + ActionResult DoDefaultAction(TrackedElement* element, + InputType input_type) override { DoAction(ActionType::kDoDefaultAction, element, input_type); - return true; + return result_; } - bool SelectTab(TrackedElement* tab_collection, - size_t index, - InputType input_type) override { + ActionResult SelectTab(TrackedElement* tab_collection, + size_t index, + InputType input_type) override { DoAction(ActionType::kSelectTab, tab_collection, input_type); - return true; + return result_; } - bool SelectDropdownItem(TrackedElement* collection, - size_t item, - InputType input_type) override { + ActionResult SelectDropdownItem(TrackedElement* collection, + size_t item, + InputType input_type) override { DoAction(ActionType::kSelectDropdownItem, collection, input_type); - return true; + return result_; } - bool EnterText(TrackedElement* element, - const std::u16string& text, - TextEntryMode mode) override { + ActionResult EnterText(TrackedElement* element, + std::u16string text, + TextEntryMode mode) override { DoAction(ActionType::kEnterText, element, InputType::kKeyboard); - return true; + return result_; } - bool ActivateSurface(TrackedElement* element) override { + ActionResult ActivateSurface(TrackedElement* element) override { DoAction(ActionType::kActivateSurface, element, InputType::kMouse); - return true; + return result_; } #if !BUILDFLAG(IS_IOS) - bool SendAccelerator(TrackedElement* element, - const Accelerator& accel) override { + ActionResult SendAccelerator(TrackedElement* element, + Accelerator accel) override { DoAction(ActionType::kSendAccelerator, element, InputType::kKeyboard); - return true; + return result_; } #endif - bool Confirm(TrackedElement* element) override { + ActionResult Confirm(TrackedElement* element) override { DoAction(ActionType::kConfirm, element, InputType::kDontCare); - return true; + return result_; } const std::vector& records() const { return records_; } @@ -122,6 +131,7 @@ class TestSimulator : public InteractionTestUtil::Simulator { element->AsA()->Activate(); } + ActionResult result_ = ActionResult::kSucceeded; std::vector records_; }; @@ -143,9 +153,9 @@ class InteractiveTestTest : public InteractiveTest { FROM_HERE, std::move(actions)); } - private: base::raw_ptr simulator_ = nullptr; + private: base::test::SingleThreadTaskEnvironment task_environment_{ base::test::SingleThreadTaskEnvironment::MainThreadType::UI}; }; @@ -556,4 +566,228 @@ TEST_F(InteractiveTestTest, NamedElement) { WithElement(kName, cb.Get()))); } +TEST_F(InteractiveTestTest, SimulatorSucceeds_SkipOnUnsupported) { + UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); + TestElement e1(kTestId1, kTestContext1); + e1.Show(); + + private_test_impl().set_aborted_callback_for_testing(aborted.Get()); + RunTestSequenceInContext( + kTestContext1, + SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest, + kSetOnIncompatibleActionMessage), + PressButton(kTestId1)); + EXPECT_FALSE(private_test_impl().sequence_skipped()); +} + +TEST_F(InteractiveTestTest, SimulatorSucceeds_IgnoreOnUnsupported) { + UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); + TestElement e1(kTestId1, kTestContext1); + e1.Show(); + + private_test_impl().set_aborted_callback_for_testing(aborted.Get()); + RunTestSequenceInContext( + kTestContext1, + SetOnIncompatibleAction(OnIncompatibleAction::kIgnoreAndContinue, + kSetOnIncompatibleActionMessage), + PressButton(kTestId1)); + EXPECT_FALSE(private_test_impl().sequence_skipped()); +} + +TEST_F(InteractiveTestTest, SimulatorFailureFails) { + UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); + TestElement e1(kTestId1, kTestContext1); + e1.Show(); + + private_test_impl().set_aborted_callback_for_testing(aborted.Get()); + simulator_->set_result(ActionResult::kFailed); + EXPECT_CALL_IN_SCOPE( + aborted, Run, + RunTestSequenceInContext(kTestContext1, PressButton(kTestId1))); + EXPECT_FALSE(private_test_impl().sequence_skipped()); +} + +TEST_F(InteractiveTestTest, SimulatorFailureFails_SkipOnUnsupported) { + UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); + TestElement e1(kTestId1, kTestContext1); + e1.Show(); + + private_test_impl().set_aborted_callback_for_testing(aborted.Get()); + simulator_->set_result(ActionResult::kFailed); + EXPECT_CALL_IN_SCOPE( + aborted, Run, + RunTestSequenceInContext( + kTestContext1, + SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest, + kSetOnIncompatibleActionMessage), + PressButton(kTestId1))); + EXPECT_FALSE(private_test_impl().sequence_skipped()); +} + +TEST_F(InteractiveTestTest, SimulatorFailureFails_IgnoreOnUnsupported) { + UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); + TestElement e1(kTestId1, kTestContext1); + e1.Show(); + + private_test_impl().set_aborted_callback_for_testing(aborted.Get()); + simulator_->set_result(ActionResult::kFailed); + EXPECT_CALL_IN_SCOPE( + aborted, Run, + RunTestSequenceInContext( + kTestContext1, + SetOnIncompatibleAction(OnIncompatibleAction::kIgnoreAndContinue, + kSetOnIncompatibleActionMessage), + PressButton(kTestId1))); + EXPECT_FALSE(private_test_impl().sequence_skipped()); +} + +TEST_F(InteractiveTestTest, SimulatorCannotSimulateFails) { + UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); + TestElement e1(kTestId1, kTestContext1); + e1.Show(); + + private_test_impl().set_aborted_callback_for_testing(aborted.Get()); + simulator_->set_result(ActionResult::kNotAttempted); + EXPECT_CALL_IN_SCOPE( + aborted, Run, + RunTestSequenceInContext(kTestContext1, PressButton(kTestId1))); + EXPECT_FALSE(private_test_impl().sequence_skipped()); +} + +TEST_F(InteractiveTestTest, SimulatorCannotSimulateFails_SkipOnUnsupported) { + UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); + TestElement e1(kTestId1, kTestContext1); + e1.Show(); + + private_test_impl().set_aborted_callback_for_testing(aborted.Get()); + simulator_->set_result(ActionResult::kNotAttempted); + EXPECT_CALL_IN_SCOPE( + aborted, Run, + RunTestSequenceInContext( + kTestContext1, + SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest, + kSetOnIncompatibleActionMessage), + PressButton(kTestId1))); + EXPECT_FALSE(private_test_impl().sequence_skipped()); +} + +TEST_F(InteractiveTestTest, SimulatorCannotSimulateFails_IgnoreOnUnsupported) { + UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); + TestElement e1(kTestId1, kTestContext1); + e1.Show(); + + private_test_impl().set_aborted_callback_for_testing(aborted.Get()); + simulator_->set_result(ActionResult::kNotAttempted); + EXPECT_CALL_IN_SCOPE( + aborted, Run, + RunTestSequenceInContext( + kTestContext1, + SetOnIncompatibleAction(OnIncompatibleAction::kIgnoreAndContinue, + kSetOnIncompatibleActionMessage), + PressButton(kTestId1))); + EXPECT_FALSE(private_test_impl().sequence_skipped()); +} + +TEST_F(InteractiveTestTest, SimulatorNotSupportedFails) { + UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); + TestElement e1(kTestId1, kTestContext1); + e1.Show(); + + private_test_impl().set_aborted_callback_for_testing(aborted.Get()); + simulator_->set_result(ActionResult::kKnownIncompatible); + EXPECT_CALL_IN_SCOPE( + aborted, Run, + RunTestSequenceInContext(kTestContext1, PressButton(kTestId1))); + EXPECT_FALSE(private_test_impl().sequence_skipped()); +} + +TEST_F(InteractiveTestTest, SimulatorNotSupportedSkipsOnUnsupported) { + UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); + TestElement e1(kTestId1, kTestContext1); + e1.Show(); + + private_test_impl().set_aborted_callback_for_testing(aborted.Get()); + simulator_->set_result(ActionResult::kKnownIncompatible); + EXPECT_CALL_IN_SCOPE( + aborted, Run, + RunTestSequenceInContext( + kTestContext1, + SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest, + kSetOnIncompatibleActionMessage), + PressButton(kTestId1))); + EXPECT_TRUE(private_test_impl().sequence_skipped()); +} + +TEST_F(InteractiveTestTest, SimulatorNotSupportedContinuesOnUnsupported) { + UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); + TestElement e1(kTestId1, kTestContext1); + e1.Show(); + + bool result = false; + + private_test_impl().set_aborted_callback_for_testing(aborted.Get()); + simulator_->set_result(ActionResult::kKnownIncompatible); + RunTestSequenceInContext( + kTestContext1, + SetOnIncompatibleAction(OnIncompatibleAction::kIgnoreAndContinue, + kSetOnIncompatibleActionMessage), + PressButton(kTestId1), + Do(base::BindLambdaForTesting([&result]() { result = true; }))); + EXPECT_TRUE(result); +} + +TEST_F(InteractiveTestTest, CanChangeOnIncompatibleAction) { + UNCALLED_MOCK_CALLBACK(InteractionSequence::AbortedCallback, aborted); + TestElement e1(kTestId1, kTestContext1); + e1.Show(); + + private_test_impl().set_aborted_callback_for_testing(aborted.Get()); + simulator_->set_result(ActionResult::kKnownIncompatible); + EXPECT_CALL_IN_SCOPE( + aborted, Run, + RunTestSequenceInContext( + kTestContext1, + // Based on previous tests, this will fall through to the next step. + SetOnIncompatibleAction(OnIncompatibleAction::kIgnoreAndContinue, + kSetOnIncompatibleActionMessage), + PressButton(kTestId1), + // By changing the incompatible mode, the step after this one should + // fail. + SetOnIncompatibleAction(OnIncompatibleAction::kFailTest, ""), + PressButton(kTestId1))); +} + +TEST_F(InteractiveTestTest, SimulatorNotSupportedHaltAndSucceedOnUnsupported) { + TestElement e1(kTestId1, kTestContext1); + e1.Show(); + + bool result = false; + + simulator_->set_result(ActionResult::kKnownIncompatible); + RunTestSequenceInContext( + kTestContext1, + SetOnIncompatibleAction(OnIncompatibleAction::kHaltTest, + kSetOnIncompatibleActionMessage), + PressButton(kTestId1), + Do(base::BindLambdaForTesting([&result]() { result = true; }))); + EXPECT_FALSE(result); +} + +TEST_F(InteractiveTestTest, ActuallySkipsTestOnSimulatorFailure) { + TestElement e1(kTestId1, kTestContext1); + e1.Show(); + simulator_->set_result(ActionResult::kKnownIncompatible); + RunTestSequenceInContext( + kTestContext1, + SetOnIncompatibleAction(OnIncompatibleAction::kSkipTest, + kSetOnIncompatibleActionMessage), + PressButton(kTestId1)); + + // Note: this test will either be marked as skipped or failed, but never + // succeeded. The important thing is that it does not fail. + if (!testing::Test::IsSkipped()) { + GTEST_FAIL(); + } +} + } // namespace ui::test diff --git a/ui/views/interaction/element_tracker_views.cc b/ui/views/interaction/element_tracker_views.cc index 3b46047c3b662f..a18b74594fc447 100644 --- a/ui/views/interaction/element_tracker_views.cc +++ b/ui/views/interaction/element_tracker_views.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include "base/containers/contains.h" #include "base/debug/stack_trace.h" @@ -33,6 +34,13 @@ TrackedElementViews::TrackedElementViews(View* view, TrackedElementViews::~TrackedElementViews() = default; +std::string TrackedElementViews::ToString() const { + auto result = TrackedElement::ToString(); + result.append(" with view "); + result.append(view()->GetClassName()); + return result; +} + DEFINE_FRAMEWORK_SPECIFIC_METADATA(TrackedElementViews) // Tracks views associated with a specific ui::ElementIdentifier, whether or not diff --git a/ui/views/interaction/element_tracker_views.h b/ui/views/interaction/element_tracker_views.h index 0682495c52bfd7..ba8562fe7da59b 100644 --- a/ui/views/interaction/element_tracker_views.h +++ b/ui/views/interaction/element_tracker_views.h @@ -6,6 +6,7 @@ #define UI_VIEWS_INTERACTION_ELEMENT_TRACKER_VIEWS_H_ #include +#include #include #include "base/gtest_prod_util.h" @@ -34,6 +35,8 @@ class VIEWS_EXPORT TrackedElementViews : public ui::TrackedElement { View* view() { return view_; } const View* view() const { return view_; } + std::string ToString() const override; + DECLARE_FRAMEWORK_SPECIFIC_METADATA() private: diff --git a/ui/views/interaction/interaction_sequence_views_unittest.cc b/ui/views/interaction/interaction_sequence_views_unittest.cc index 87e8e036a0879e..15ad99363d6b18 100644 --- a/ui/views/interaction/interaction_sequence_views_unittest.cc +++ b/ui/views/interaction/interaction_sequence_views_unittest.cc @@ -538,7 +538,8 @@ TEST_F(InteractionSequenceViewsTest, TransitionToMenuAndActivateMenuItem) { ui::test::InteractionTestUtil test_util; test_util.AddSimulator( std::make_unique()); - test_util.SelectMenuItem(menu_element_.get()); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util.SelectMenuItem(menu_element_.get())); }); } diff --git a/ui/views/interaction/interaction_test_util_views.cc b/ui/views/interaction/interaction_test_util_views.cc index 8fd80793143d66..7fbbd96395bcdd 100644 --- a/ui/views/interaction/interaction_test_util_views.cc +++ b/ui/views/interaction/interaction_test_util_views.cc @@ -78,19 +78,22 @@ class DropdownItemSelector { // item. void SelectItem() { CHECK(!run_loop_.running()); - CHECK(!success_.has_value()); + CHECK(!result_.has_value()); run_loop_.Run(); } // Returns whether the operation succeeded or failed. - bool success() const { return success_.value_or(false); } + ui::test::ActionResult result() const { + return result_.value_or(ui::test::ActionResult::kFailed); + } private: // Responds to a new widget being shown. The assumption is that this widget is // the combobox dropdown. If it is not, the follow-up call will fail. void OnWidgetShown(Widget* widget) { - if (widget_ || success_.has_value()) + if (widget_ || result_.has_value()) { return; + } widget_ = widget; base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( @@ -101,18 +104,19 @@ class DropdownItemSelector { // Detects when a widget is hidden. Fails the operation if this was the drop- // down widget and the item has not yet been selected. void OnWidgetHidden(Widget* widget) { - if (success_.has_value() || widget_ != widget) + if (result_.has_value() || widget_ != widget) { return; + } LOG(ERROR) << "Widget closed before selection took place."; - SetSuccess(false); + SetResult(ui::test::ActionResult::kFailed); } // Actually finds and selects the item in the drop-down. If it is not present // or cannot be selected, fails the operation. void SelectItemImpl() { CHECK(widget_); - CHECK(!success_.has_value()); + CHECK(!result_.has_value()); // Because this widget was just shown, it may not be laid out yet. widget_->LayoutRootViewIfNecessary(); @@ -124,26 +128,37 @@ class DropdownItemSelector { widget_ = nullptr; // Try to select the item. - if (simulator_->SelectMenuItem( - ElementTrackerViews::GetInstance()->GetElementForView(menu_item, - true), - input_type_)) { - SetSuccess(true); - } else { - LOG(ERROR) << "Unable to select dropdown menu item."; - SetSuccess(false); + const auto result = simulator_->SelectMenuItem( + ElementTrackerViews::GetInstance()->GetElementForView(menu_item, + true), + input_type_); + SetResult(result); + switch (result) { + case ui::test::ActionResult::kFailed: + LOG(ERROR) << "Unable to select dropdown menu item."; + break; + case ui::test::ActionResult::kNotAttempted: + NOTREACHED(); + break; + case ui::test::ActionResult::kKnownIncompatible: + LOG(WARNING) + << "Select dropdown item not available on this platform with " + "input type " + << input_type_; + break; + case ui::test::ActionResult::kSucceeded: + break; } } else { LOG(ERROR) << "Dropdown menu item not found."; - SetSuccess(false); + SetResult(ui::test::ActionResult::kFailed); } } - // Sets the success or failure state and aborts `run_loop_`. Should only ever - // be called once. - void SetSuccess(bool success) { - CHECK(!success_.has_value()); - success_ = success; + // Sets the result and aborts `run_loop_`. Should only ever be called once. + void SetResult(ui::test::ActionResult result) { + CHECK(!result_.has_value()); + result_ = result; widget_ = nullptr; weak_ptr_factory_.InvalidateWeakPtrs(); run_loop_.Quit(); @@ -172,7 +187,7 @@ class DropdownItemSelector { const size_t item_index_; base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed}; AnyWidgetObserver observer_{views::test::AnyWidgetTestPasskey()}; // IN-TEST - absl::optional success_; + absl::optional result_; base::raw_ptr widget_ = nullptr; base::WeakPtrFactory weak_ptr_factory_{this}; }; @@ -181,10 +196,10 @@ gfx::Point GetCenter(views::View* view) { return view->GetLocalBounds().CenterPoint(); } -void SendDefaultAction(View* target) { +bool SendDefaultAction(View* target) { ui::AXActionData action; action.action = ax::mojom::Action::kDoDefault; - CHECK(target->HandleAccessibleAction(action)); + return target->HandleAccessibleAction(action); } // Sends a mouse click to the specified `target`. @@ -240,28 +255,29 @@ bool SendKeyPress(View* view, ui::KeyboardCode code, int flags = ui::EF_NONE) { } // namespace -bool InteractionTestUtilSimulatorViews::PressButton(ui::TrackedElement* element, - InputType input_type) { +ui::test::ActionResult InteractionTestUtilSimulatorViews::PressButton( + ui::TrackedElement* element, + InputType input_type) { if (!element->IsA()) - return false; + return ui::test::ActionResult::kNotAttempted; auto* const button = Button::AsButton(element->AsA()->view()); if (!button) - return false; + return ui::test::ActionResult::kNotAttempted; PressButton(button, input_type); - return true; + return ui::test::ActionResult::kSucceeded; } -bool InteractionTestUtilSimulatorViews::SelectMenuItem( +ui::test::ActionResult InteractionTestUtilSimulatorViews::SelectMenuItem( ui::TrackedElement* element, InputType input_type) { if (!element->IsA()) - return false; + return ui::test::ActionResult::kNotAttempted; auto* const menu_item = AsViewClass(element->AsA()->view()); if (!menu_item) - return false; + return ui::test::ActionResult::kNotAttempted; #if BUILDFLAG(IS_MAC) // Keyboard input isn't reliable on Mac for submenus, so unless the test @@ -296,19 +312,23 @@ bool InteractionTestUtilSimulatorViews::SelectMenuItem( break; } } - return true; + return ui::test::ActionResult::kSucceeded; } -bool InteractionTestUtilSimulatorViews::DoDefaultAction( +ui::test::ActionResult InteractionTestUtilSimulatorViews::DoDefaultAction( ui::TrackedElement* element, InputType input_type) { if (!element->IsA()) - return false; - DoDefaultAction(element->AsA()->view(), input_type); - return true; + return ui::test::ActionResult::kNotAttempted; + if (!DoDefaultAction(element->AsA()->view(), + input_type)) { + LOG(ERROR) << "Failed to send default action to " << *element; + return ui::test::ActionResult::kFailed; + } + return ui::test::ActionResult::kSucceeded; } -bool InteractionTestUtilSimulatorViews::SelectTab( +ui::test::ActionResult InteractionTestUtilSimulatorViews::SelectTab( ui::TrackedElement* tab_collection, size_t index, InputType input_type) { @@ -317,18 +337,25 @@ bool InteractionTestUtilSimulatorViews::SelectTab( // kind of simulator specific to browser code, so if this is not a supported // View type, just return false instead of sending an error. if (!tab_collection->IsA()) - return false; + return ui::test::ActionResult::kNotAttempted; auto* const pane = views::AsViewClass( tab_collection->AsA()->view()); if (!pane) - return false; + return ui::test::ActionResult::kNotAttempted; // Unlike with the element type, an out-of-bounds tab is always an error. auto* const tab = pane->GetTabAt(index); - CHECK(tab); + if (!tab) { + LOG(ERROR) << "Tab index " << index << " out of range, there are " + << pane->GetTabCount() << " tabs."; + return ui::test::ActionResult::kFailed; + } switch (input_type) { case ui::test::InteractionTestUtil::InputType::kDontCare: - SendDefaultAction(tab); + if (!SendDefaultAction(tab)) { + LOG(ERROR) << "Failed to send default action to tab."; + return ui::test::ActionResult::kFailed; + } break; case ui::test::InteractionTestUtil::InputType::kMouse: SendMouseClick(tab, GetCenter(tab)); @@ -355,28 +382,35 @@ bool InteractionTestUtilSimulatorViews::SelectTab( auto* const current_tab = pane->GetTabAt(pane->GetSelectedTabIndex()); SendKeyPress(current_tab, code); } - CHECK_EQ(index, pane->GetSelectedTabIndex()); + if (index != pane->GetSelectedTabIndex()) { + LOG(ERROR) << "Unable to cycle through tabs to reach index " << index; + return ui::test::ActionResult::kFailed; + } } break; } } - return true; + return ui::test::ActionResult::kSucceeded; } -bool InteractionTestUtilSimulatorViews::SelectDropdownItem( +ui::test::ActionResult InteractionTestUtilSimulatorViews::SelectDropdownItem( ui::TrackedElement* dropdown, size_t index, InputType input_type) { if (!dropdown->IsA()) - return false; + return ui::test::ActionResult::kNotAttempted; auto* const view = dropdown->AsA()->view(); auto* const combobox = views::AsViewClass(view); auto* const editable_combobox = views::AsViewClass(view); if (!combobox && !editable_combobox) - return false; + return ui::test::ActionResult::kNotAttempted; auto* const model = combobox ? combobox->GetModel() : editable_combobox->combobox_model_.get(); - CHECK_LT(index, model->GetItemCount()); + if (index >= model->GetItemCount()) { + LOG(ERROR) << "Item index " << index << " is out of range, there are " + << model->GetItemCount() << " items."; + return ui::test::ActionResult::kFailed; + } // InputType::kDontCare is implemented in a way that is safe across all // platforms and most test environments; it does not rely on popping up the @@ -387,15 +421,15 @@ bool InteractionTestUtilSimulatorViews::SelectDropdownItem( } else { editable_combobox->SetText(model->GetItemAt(index)); } - return true; + return ui::test::ActionResult::kSucceeded; } // For specific input types, the dropdown will be popped out. Because of // asynchronous and event-handling issues, this is not yet supported on Mac. #if BUILDFLAG(IS_MAC) - LOG(ERROR) << "SelectDropdownItem(): " - "only InputType::kDontCare is supported on Mac."; - return false; + LOG(WARNING) << "SelectDropdownItem(): " + "only InputType::kDontCare is supported on Mac."; + return ui::test::ActionResult::kKnownIncompatible; #else // This is required in case we want to repeatedly test a combobox; otherwise @@ -415,8 +449,11 @@ bool InteractionTestUtilSimulatorViews::SelectDropdownItem( if (arrow) { PressButton(arrow, input_type); } else { - CHECK(editable_combobox) << "Only EditableCombobox should have the option " - "to completely remove its arrow."; + if (!editable_combobox) { + LOG(ERROR) << "Only EditableCombobox should have the option to " + "completely remove its arrow."; + return ui::test::ActionResult::kFailed; + } // Editable comboboxes without visible arrows exist, but are weird. switch (input_type) { case InputType::kDontCare: @@ -425,22 +462,23 @@ bool InteractionTestUtilSimulatorViews::SelectDropdownItem( SendKeyPress(editable_combobox->textfield_, ui::VKEY_DOWN); break; default: - LOG(ERROR) << "Mouse and touch input are not supported for " - "comboboxes without visible arrows."; - return false; + LOG(WARNING) << "Mouse and touch input are not supported for " + "comboboxes without visible arrows."; + return ui::test::ActionResult::kNotAttempted; } } selector.SelectItem(); - return selector.success(); + return selector.result(); #endif } -bool InteractionTestUtilSimulatorViews::EnterText(ui::TrackedElement* element, - const std::u16string& text, - TextEntryMode mode) { +ui::test::ActionResult InteractionTestUtilSimulatorViews::EnterText( + ui::TrackedElement* element, + std::u16string text, + TextEntryMode mode) { if (!element->IsA()) - return false; + return ui::test::ActionResult::kNotAttempted; auto* const view = element->AsA()->view(); // Currently, Textfields (and derived types like Textareas) are supported, as @@ -449,74 +487,75 @@ bool InteractionTestUtilSimulatorViews::EnterText(ui::TrackedElement* element, if (!textfield && IsViewClass(view)) textfield = AsViewClass(view)->textfield_; - if (textfield) { - if (textfield->GetReadOnly()) { - LOG(ERROR) << "Cannot set text on read-only textfield."; - return false; - } + if (!textfield) { + return ui::test::ActionResult::kNotAttempted; + } - // Textfield does not expose all of the power of RenderText so some care has - // to be taken in positioning the input caret using the methods available to - // this class. - switch (mode) { - case TextEntryMode::kAppend: { - // Determine the start and end of the selectable range, and position the - // caret immediately after the end of the range. This approach does not - // make any assumptions about the indexing mode of the text, - // multi-codepoint characters, etc. - textfield->SelectAll(false); - auto range = textfield->GetSelectedRange(); - range.set_start(range.end()); - textfield->SetSelectedRange(range); - break; - } - case TextEntryMode::kInsertOrReplace: - // No action needed; keep selection and cursor as they are. - break; - case TextEntryMode::kReplaceAll: - textfield->SelectAll(false); - break; - } + if (textfield->GetReadOnly()) { + LOG(ERROR) << "Cannot set text on read-only textfield."; + return ui::test::ActionResult::kFailed; + } - // This is an IME method that is the closest thing to inserting text from - // the user rather than setting it programmatically. - textfield->InsertText( - text, - ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText); - return true; + // Textfield does not expose all of the power of RenderText so some care has + // to be taken in positioning the input caret using the methods available to + // this class. + switch (mode) { + case TextEntryMode::kAppend: { + // Determine the start and end of the selectable range, and position the + // caret immediately after the end of the range. This approach does not + // make any assumptions about the indexing mode of the text, + // multi-codepoint characters, etc. + textfield->SelectAll(false); + auto range = textfield->GetSelectedRange(); + range.set_start(range.end()); + textfield->SetSelectedRange(range); + break; + } + case TextEntryMode::kInsertOrReplace: + // No action needed; keep selection and cursor as they are. + break; + case TextEntryMode::kReplaceAll: + textfield->SelectAll(false); + break; } - return false; + // This is an IME method that is the closest thing to inserting text from + // the user rather than setting it programmatically. + textfield->InsertText( + text, + ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText); + return ui::test::ActionResult::kSucceeded; } -bool InteractionTestUtilSimulatorViews::ActivateSurface( +ui::test::ActionResult InteractionTestUtilSimulatorViews::ActivateSurface( ui::TrackedElement* element) { if (!element->IsA()) - return false; + return ui::test::ActionResult::kNotAttempted; auto* const widget = element->AsA()->view()->GetWidget(); views::test::WidgetActivationWaiter waiter(widget, true); widget->Activate(); waiter.Wait(); - return true; + return ui::test::ActionResult::kSucceeded; } -bool InteractionTestUtilSimulatorViews::SendAccelerator( +ui::test::ActionResult InteractionTestUtilSimulatorViews::SendAccelerator( ui::TrackedElement* element, - const ui::Accelerator& accelerator) { + ui::Accelerator accelerator) { if (!element->IsA()) - return false; + return ui::test::ActionResult::kNotAttempted; element->AsA() ->view() ->GetFocusManager() ->ProcessAccelerator(accelerator); - return true; + return ui::test::ActionResult::kSucceeded; } -bool InteractionTestUtilSimulatorViews::Confirm(ui::TrackedElement* element) { +ui::test::ActionResult InteractionTestUtilSimulatorViews::Confirm( + ui::TrackedElement* element) { if (!element->IsA()) - return false; + return ui::test::ActionResult::kNotAttempted; auto* const view = element->AsA()->view(); // Currently, only dialogs can be confirmed. Fetch the delegate and call @@ -529,32 +568,31 @@ bool InteractionTestUtilSimulatorViews::Confirm(ui::TrackedElement* element) { } if (!delegate) - return false; + return ui::test::ActionResult::kNotAttempted; if (!delegate->GetOkButton()) { LOG(ERROR) << "Confirm(): cannot confirm dialog that has no OK button."; - return false; + return ui::test::ActionResult::kFailed; } delegate->AcceptDialog(); - return true; + return ui::test::ActionResult::kSucceeded; } -void InteractionTestUtilSimulatorViews::DoDefaultAction(View* view, +bool InteractionTestUtilSimulatorViews::DoDefaultAction(View* view, InputType input_type) { switch (input_type) { case ui::test::InteractionTestUtil::InputType::kDontCare: - SendDefaultAction(view); - break; + return SendDefaultAction(view); case ui::test::InteractionTestUtil::InputType::kMouse: SendMouseClick(view, GetCenter(view)); - break; + return true; case ui::test::InteractionTestUtil::InputType::kTouch: SendTapGesture(view, GetCenter(view)); - break; + return true; case ui::test::InteractionTestUtil::InputType::kKeyboard: SendKeyPress(view, ui::VKEY_SPACE); - break; + return true; } } @@ -575,6 +613,4 @@ void InteractionTestUtilSimulatorViews::PressButton(Button* button, } } -// static - } // namespace views::test diff --git a/ui/views/interaction/interaction_test_util_views.h b/ui/views/interaction/interaction_test_util_views.h index f0c8f9fd0a7082..26c4d4682032a0 100644 --- a/ui/views/interaction/interaction_test_util_views.h +++ b/ui/views/interaction/interaction_test_util_views.h @@ -29,24 +29,25 @@ class InteractionTestUtilSimulatorViews ~InteractionTestUtilSimulatorViews() override; // ui::test::InteractionTestUtil::Simulator: - bool PressButton(ui::TrackedElement* element, InputType input_type) override; - bool SelectMenuItem(ui::TrackedElement* element, - InputType input_type) override; - bool DoDefaultAction(ui::TrackedElement* element, - InputType input_type) override; - bool SelectTab(ui::TrackedElement* tab_collection, - size_t index, - InputType input_type) override; - bool SelectDropdownItem(ui::TrackedElement* dropdown, - size_t index, - InputType input_type) override; - bool EnterText(ui::TrackedElement* element, - const std::u16string& text, - TextEntryMode mode) override; - bool ActivateSurface(ui::TrackedElement* element) override; - bool SendAccelerator(ui::TrackedElement* element, - const ui::Accelerator& accelerator) override; - bool Confirm(ui::TrackedElement* element) override; + ui::test::ActionResult PressButton(ui::TrackedElement* element, + InputType input_type) override; + ui::test::ActionResult SelectMenuItem(ui::TrackedElement* element, + InputType input_type) override; + ui::test::ActionResult DoDefaultAction(ui::TrackedElement* element, + InputType input_type) override; + ui::test::ActionResult SelectTab(ui::TrackedElement* tab_collection, + size_t index, + InputType input_type) override; + ui::test::ActionResult SelectDropdownItem(ui::TrackedElement* dropdown, + size_t index, + InputType input_type) override; + ui::test::ActionResult EnterText(ui::TrackedElement* element, + std::u16string text, + TextEntryMode mode) override; + ui::test::ActionResult ActivateSurface(ui::TrackedElement* element) override; + ui::test::ActionResult SendAccelerator(ui::TrackedElement* element, + ui::Accelerator accelerator) override; + ui::test::ActionResult Confirm(ui::TrackedElement* element) override; // Convenience method for tests that need to simulate a button press and have // direct access to the button. @@ -54,7 +55,7 @@ class InteractionTestUtilSimulatorViews InputType input_type = InputType::kDontCare); // As above, but for non-button Views. - static void DoDefaultAction(View* view, + static bool DoDefaultAction(View* view, InputType input_type = InputType::kDontCare); }; diff --git a/ui/views/interaction/interaction_test_util_views_unittest.cc b/ui/views/interaction/interaction_test_util_views_unittest.cc index b69300f319bebe..3469c4dcaefb96 100644 --- a/ui/views/interaction/interaction_test_util_views_unittest.cc +++ b/ui/views/interaction/interaction_test_util_views_unittest.cc @@ -218,12 +218,12 @@ TEST_P(InteractionTestUtilViewsTest, PressButton) { auto* const button = contents_->AddChildView(std::make_unique( Button::PressedCallback(pressed.Get()), u"Button")); widget_->LayoutRootViewIfNecessary(); - EXPECT_CALL_IN_SCOPE( - pressed, Run, - test_util_->PressButton( - views::ElementTrackerViews::GetInstance()->GetElementForView(button, - true), - GetParam())); + EXPECT_CALL_IN_SCOPE(pressed, Run, + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->PressButton( + views::ElementTrackerViews::GetInstance() + ->GetElementForView(button, true), + GetParam()))); } TEST_P(InteractionTestUtilViewsTest, SelectMenuItem) { @@ -234,12 +234,12 @@ TEST_P(InteractionTestUtilViewsTest, SelectMenuItem) { kMenuItemIdentifier, ElementTrackerViews::GetContextForWidget(widget_.get()), pressed.Get()); - EXPECT_CALL_IN_SCOPE( - pressed, Run, - test_util_->SelectMenuItem( - views::ElementTrackerViews::GetInstance()->GetElementForView( - menu_item_), - GetParam())); + EXPECT_CALL_IN_SCOPE(pressed, Run, + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SelectMenuItem( + views::ElementTrackerViews::GetInstance() + ->GetElementForView(menu_item_), + GetParam()))); } TEST_P(InteractionTestUtilViewsTest, DoDefault) { @@ -249,9 +249,10 @@ TEST_P(InteractionTestUtilViewsTest, DoDefault) { auto* const view = contents_->AddChildView(std::make_unique()); widget_->LayoutRootViewIfNecessary(); - test_util_->DoDefaultAction( - views::ElementTrackerViews::GetInstance()->GetElementForView(view, - true)); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->DoDefaultAction( + views::ElementTrackerViews::GetInstance()->GetElementForView( + view, true))); EXPECT_TRUE(view->activated()); } else { @@ -265,12 +266,12 @@ TEST_P(InteractionTestUtilViewsTest, DoDefault) { auto* const button = contents_->AddChildView(std::make_unique( Button::PressedCallback(pressed.Get()), u"Button")); widget_->LayoutRootViewIfNecessary(); - EXPECT_CALL_IN_SCOPE( - pressed, Run, - test_util_->DoDefaultAction( - views::ElementTrackerViews::GetInstance()->GetElementForView(button, - true), - GetParam())); + EXPECT_CALL_IN_SCOPE(pressed, Run, + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->DoDefaultAction( + views::ElementTrackerViews::GetInstance() + ->GetElementForView(button, true), + GetParam()))); } } @@ -284,11 +285,14 @@ TEST_P(InteractionTestUtilViewsTest, SelectTab) { Button::PressedCallback(), u"Button")); auto* const pane_el = views::ElementTrackerViews::GetInstance()->GetElementForView(pane, true); - test_util_->SelectTab(pane_el, 2, GetParam()); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SelectTab(pane_el, 2, GetParam())); EXPECT_EQ(2U, pane->GetSelectedTabIndex()); - test_util_->SelectTab(pane_el, 0, GetParam()); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SelectTab(pane_el, 0, GetParam())); EXPECT_EQ(0U, pane->GetSelectedTabIndex()); - test_util_->SelectTab(pane_el, 1, GetParam()); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SelectTab(pane_el, 1, GetParam())); EXPECT_EQ(1U, pane->GetSelectedTabIndex()); } @@ -305,11 +309,14 @@ TEST_P(InteractionTestUtilViewsTest, SelectDropdownItem_Combobox) { widget_->LayoutRootViewIfNecessary(); auto* const box_el = views::ElementTrackerViews::GetInstance()->GetElementForView(box, true); - test_util_->SelectDropdownItem(box_el, 2, GetParam()); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SelectDropdownItem(box_el, 2, GetParam())); EXPECT_EQ(2U, box->GetSelectedIndex()); - test_util_->SelectDropdownItem(box_el, 0, GetParam()); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SelectDropdownItem(box_el, 0, GetParam())); EXPECT_EQ(0U, box->GetSelectedIndex()); - test_util_->SelectDropdownItem(box_el, 1, GetParam()); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SelectDropdownItem(box_el, 1, GetParam())); EXPECT_EQ(1U, box->GetSelectedIndex()); } @@ -326,11 +333,14 @@ TEST_P(InteractionTestUtilViewsTest, SelectDropdownItem_EditableCombobox) { widget_->LayoutRootViewIfNecessary(); auto* const box_el = views::ElementTrackerViews::GetInstance()->GetElementForView(box, true); - test_util_->SelectDropdownItem(box_el, 2, GetParam()); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SelectDropdownItem(box_el, 2, GetParam())); EXPECT_EQ(kComboBoxItem3, box->GetText()); - test_util_->SelectDropdownItem(box_el, 0, GetParam()); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SelectDropdownItem(box_el, 0, GetParam())); EXPECT_EQ(kComboBoxItem1, box->GetText()); - test_util_->SelectDropdownItem(box_el, 1, GetParam()); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SelectDropdownItem(box_el, 1, GetParam())); EXPECT_EQ(kComboBoxItem2, box->GetText()); } @@ -348,11 +358,14 @@ TEST_P(InteractionTestUtilViewsTest, SelectDropdownItem_Combobox_NoArrow) { widget_->LayoutRootViewIfNecessary(); auto* const box_el = views::ElementTrackerViews::GetInstance()->GetElementForView(box, true); - test_util_->SelectDropdownItem(box_el, 2, GetParam()); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SelectDropdownItem(box_el, 2, GetParam())); EXPECT_EQ(2U, box->GetSelectedIndex()); - test_util_->SelectDropdownItem(box_el, 0, GetParam()); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SelectDropdownItem(box_el, 0, GetParam())); EXPECT_EQ(0U, box->GetSelectedIndex()); - test_util_->SelectDropdownItem(box_el, 1, GetParam()); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SelectDropdownItem(box_el, 1, GetParam())); EXPECT_EQ(1U, box->GetSelectedIndex()); } @@ -378,11 +391,14 @@ TEST_P(InteractionTestUtilViewsTest, box->SetAccessibleName(u"Editable Combobox"); auto* const box_el = views::ElementTrackerViews::GetInstance()->GetElementForView(box, true); - test_util_->SelectDropdownItem(box_el, 2, GetParam()); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SelectDropdownItem(box_el, 2, GetParam())); EXPECT_EQ(kComboBoxItem3, box->GetText()); - test_util_->SelectDropdownItem(box_el, 0, GetParam()); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SelectDropdownItem(box_el, 0, GetParam())); EXPECT_EQ(kComboBoxItem1, box->GetText()); - test_util_->SelectDropdownItem(box_el, 1, GetParam()); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SelectDropdownItem(box_el, 1, GetParam())); EXPECT_EQ(kComboBoxItem2, box->GetText()); } @@ -394,19 +410,25 @@ TEST_F(InteractionTestUtilViewsTest, EnterText_Textfield) { auto* const edit_el = views::ElementTrackerViews::GetInstance()->GetElementForView(edit, true); - test_util_->EnterText(edit_el, u"abcd"); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->EnterText(edit_el, u"abcd")); EXPECT_EQ(u"abcd", edit->GetText()); - test_util_->EnterText( - edit_el, u"efgh", - ui::test::InteractionTestUtil::TextEntryMode::kReplaceAll); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->EnterText( + edit_el, u"efgh", + ui::test::InteractionTestUtil::TextEntryMode::kReplaceAll)); EXPECT_EQ(u"efgh", edit->GetText()); - test_util_->EnterText(edit_el, u"abcd", - ui::test::InteractionTestUtil::TextEntryMode::kAppend); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->EnterText( + edit_el, u"abcd", + ui::test::InteractionTestUtil::TextEntryMode::kAppend)); EXPECT_EQ(u"efghabcd", edit->GetText()); edit->SetSelectedRange(gfx::Range(2, 6)); - test_util_->EnterText( - edit_el, u"1234", - ui::test::InteractionTestUtil::TextEntryMode::kInsertOrReplace); + EXPECT_EQ( + ui::test::ActionResult::kSucceeded, + test_util_->EnterText( + edit_el, u"1234", + ui::test::InteractionTestUtil::TextEntryMode::kInsertOrReplace)); EXPECT_EQ(u"ef1234cd", edit->GetText()); } @@ -419,19 +441,25 @@ TEST_F(InteractionTestUtilViewsTest, EnterText_EditableCombobox) { auto* const box_el = views::ElementTrackerViews::GetInstance()->GetElementForView(box, true); - test_util_->EnterText(box_el, u"abcd"); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->EnterText(box_el, u"abcd")); EXPECT_EQ(u"abcd", box->GetText()); - test_util_->EnterText( - box_el, u"efgh", - ui::test::InteractionTestUtil::TextEntryMode::kReplaceAll); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->EnterText( + box_el, u"efgh", + ui::test::InteractionTestUtil::TextEntryMode::kReplaceAll)); EXPECT_EQ(u"efgh", box->GetText()); - test_util_->EnterText(box_el, u"abcd", - ui::test::InteractionTestUtil::TextEntryMode::kAppend); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->EnterText( + box_el, u"abcd", + ui::test::InteractionTestUtil::TextEntryMode::kAppend)); EXPECT_EQ(u"efghabcd", box->GetText()); box->SelectRange(gfx::Range(2, 6)); - test_util_->EnterText( - box_el, u"1234", - ui::test::InteractionTestUtil::TextEntryMode::kInsertOrReplace); + EXPECT_EQ( + ui::test::ActionResult::kSucceeded, + test_util_->EnterText( + box_el, u"1234", + ui::test::InteractionTestUtil::TextEntryMode::kInsertOrReplace)); EXPECT_EQ(u"ef1234cd", box->GetText()); } @@ -449,7 +477,8 @@ TEST_F(InteractionTestUtilViewsTest, ActivateSurface) { WidgetDestroyedWaiter closed_waiter(widget); auto* const view_el = ElementTrackerViews::GetInstance()->GetElementForView(contents_, true); - test_util_->ActivateSurface(view_el); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->ActivateSurface(view_el)); closed_waiter.Wait(); } @@ -460,9 +489,11 @@ TEST_F(InteractionTestUtilViewsTest, SendAccelerator) { contents_->AddChildView(std::make_unique(accel)); auto* const view_el = ElementTrackerViews::GetInstance()->GetElementForView(view, true); - test_util_->SendAccelerator(view_el, accel2); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SendAccelerator(view_el, accel2)); EXPECT_FALSE(view->pressed()); - test_util_->SendAccelerator(view_el, accel); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->SendAccelerator(view_el, accel)); EXPECT_TRUE(view->pressed()); } @@ -483,7 +514,8 @@ TEST_F(InteractionTestUtilViewsTest, Confirm) { true); EXPECT_CALL_IN_SCOPE(accept, Run, { - test_util_->Confirm(dialog_el); + EXPECT_EQ(ui::test::ActionResult::kSucceeded, + test_util_->Confirm(dialog_el)); WidgetDestroyedWaiter closed_waiter(widget); closed_waiter.Wait(); });