| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef UI_BASE_INTERACTION_INTERACTION_TEST_UTIL_H_ |
| #define UI_BASE_INTERACTION_INTERACTION_TEST_UTIL_H_ |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "build/build_config.h" |
| #include "ui/base/interaction/element_tracker.h" |
| |
| #if !BUILDFLAG(IS_IOS) |
| #include "ui/base/accelerators/accelerator.h" |
| #include "ui/events/keycodes/keyboard_codes.h" |
| #endif |
| |
| 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. |
| // |
| // Simulators are checked in the order they are added, so if more than one |
| // simulator can handle a particular action, add the one that has the more |
| // specific/desired behavior first. |
| // |
| // Example usage: |
| // |
| // class MyTest { |
| // void SetUp() override { |
| // test_util_.AddSimulator( |
| // std::make_unique<InteractionTestUtilSimulatorViews>()); |
| // #if BUILDFLAG(IS_MAC) |
| // test_util_.AddSimulator( |
| // std::make_unique<InteractionTestUtilSimulatorMac>()); |
| // #endif |
| // ... |
| // } |
| // InteractionTestUtil test_util_; |
| // }; |
| // |
| // TEST_F(MyTest, TestClickButton) { |
| // ... |
| // step.SetStartCallback(base::BindLambdaForTesting([&] ( |
| // InteractionSequence* seq, TrackedElement* element) { |
| // ActionResult result = test_util_.PressButton(element); |
| // if (result == ActionResult::kError) |
| // seq->FailForTesting(); |
| // else if (result = ActionResult::kNotSupportedOnThisPlatform) |
| // seq->SkipForTesting(); |
| // })) |
| // ... |
| // } |
| // |
| class InteractionTestUtil { |
| public: |
| // Indicates the type of input we want to apply to an element. Default in most |
| // cases is `kDontCare` which will use the most reliable form of input (or may |
| // even call code that directly simulates e.g. a button press). |
| // |
| // Only use values other than `kDontCare` if you REALLY want to test a |
| // specific mode of input, as not all inputs will be supported for all |
| // frameworks or platforms. |
| enum class InputType { |
| // Simulate the input in the most reliable way, which could be through |
| // sending an input event or calling code that directly simulates the |
| // interaction. |
| kDontCare, |
| // Simulate the input explicitly via mouse events. |
| kMouse, |
| // Simulate the input explicitly via kayboard events. |
| kKeyboard, |
| // Simulate the input explicitly via touch events. |
| kTouch, |
| // If values are added to the enumeration, update this value. |
| kMaxValue = kTouch |
| }; |
| |
| // How should text be sent to a text input? |
| enum class TextEntryMode { |
| // Replaces all of the existing text with the new text. |
| kReplaceAll, |
| // Inserts the new text at the current cursor position, replacing any |
| // existing selection. |
| kInsertOrReplace, |
| // Appends the new text to the end of the existing text. |
| kAppend |
| }; |
| |
| // Provides framework-agnostic ways to send common input to the UI, such as |
| // clicking buttons, typing text, etc. |
| // |
| // Framework-specific implementations will need to be provided to each |
| // InteractionTestUtil instance you are using for testing. |
| class Simulator { |
| public: |
| Simulator() = default; |
| virtual ~Simulator() = default; |
| Simulator(const Simulator&) = delete; |
| void operator=(const Simulator&) = delete; |
| |
| using InputType = InteractionTestUtil::InputType; |
| using TextEntryMode = InteractionTestUtil::TextEntryMode; |
| |
| // 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. |
| 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. |
| 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. |
| 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. The index after the selection is verified; if for whatever |
| // reason it should not be `index`, specify |
| // `expected_index_after_selection`. |
| virtual ActionResult SelectTab( |
| TrackedElement* tab_collection, |
| size_t index, |
| InputType input_type, |
| std::optional<size_t> expected_index_after_selection); |
| |
| // Tries to select item `index` in `dropdown`. The collection could be |
| // a listbox, combobox, or similar. Note that `index` is zero-indexed. |
| virtual ActionResult SelectDropdownItem(TrackedElement* dropdown, |
| size_t index, |
| InputType input_type); |
| |
| // Sets or modifies the text of a text box, editable combobox, etc. |
| virtual ActionResult EnterText(TrackedElement* element, |
| std::u16string text, |
| TextEntryMode mode); |
| |
| // Activates the surface containing `element`. |
| virtual ActionResult ActivateSurface(TrackedElement* element); |
| |
| // Focuses `element` within its surface. Does not necessarily activate the |
| // surface. Note that on some platforms, `element` may not actually report |
| // as focused until its surface is subsequently activated. |
| virtual ActionResult FocusElement(TrackedElement* element); |
| |
| #if !BUILDFLAG(IS_IOS) |
| |
| // Sends the given accelerator to the surface containing the element. |
| virtual ActionResult SendAccelerator(TrackedElement* element, |
| Accelerator accelerator); |
| |
| // Sends keypress with `key` and `flags` to `element` or its surface. |
| virtual ActionResult SendKeyPress(TrackedElement* element, |
| KeyboardCode key, |
| int flags); |
| |
| #endif // !BUILDFLAG(IS_IOS) |
| |
| // Sends a "confirm" input to `element`, e.g. a RETURN keypress. |
| virtual ActionResult Confirm(TrackedElement* element); |
| }; |
| |
| InteractionTestUtil(); |
| virtual ~InteractionTestUtil(); |
| InteractionTestUtil(const InteractionTestUtil&) = delete; |
| void operator=(const InteractionTestUtil&) = delete; |
| |
| // Adds an input simulator for a specific framework. |
| template <class T> |
| T* AddSimulator(std::unique_ptr<T> simulator) { |
| T* const result = simulator.get(); |
| simulators_.emplace_back(std::move(simulator)); |
| return result; |
| } |
| |
| // Simulate a button press on `element`. Will fail if `element` is not a |
| // button or if `input_type` is not supported. |
| 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. |
| 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. The index after the |
| // selection will be verified; if for whatever reason it should not be |
| // `index`, specify `expected_index_after_selection`. |
| ActionResult SelectTab( |
| TrackedElement* tab_collection, |
| size_t index, |
| InputType input_type = InputType::kDontCare, |
| std::optional<size_t> expected_index_after_selection = std::nullopt); |
| |
| // Simulate selecting item `index` in `dropdown`. The collection could be |
| // a listbox, combobox, or similar. Will fail if the target object is not a |
| // supported type, if `index` is out of bounds, or if `input_type` is not |
| // supported. |
| // |
| // Note that if `input_type` is kDontCare, the approach with the broadest |
| // possible compatibility will be used, possibly bypassing the dropdown menu |
| // associated with the element. This is because dropdown menus vary in |
| // 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. |
| 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. |
| 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. |
| ActionResult EnterText(TrackedElement* element, |
| std::u16string text, |
| TextEntryMode mode = TextEntryMode::kReplaceAll); |
| |
| // Activates the surface containing `element`. Prefer to use only in |
| // single-process test fixtures like interactive_ui_tests, especially for |
| // browser windows (as bringing a browser window to the front may require some |
| // very aggressive system calls in certain cases and on certain platforms). |
| ActionResult ActivateSurface(TrackedElement* element); |
| |
| // Focuses `element` within its surface. Does not necessarily activate the |
| // surface. Note that on some platforms, `element` may not actually report as |
| // focused until its surface is subsequently activated. |
| virtual ActionResult FocusElement(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. |
| ActionResult SendAccelerator(TrackedElement* element, |
| Accelerator accelerator); |
| |
| // Sends key press `key` with `flags` to `element` or its surface. |
| ActionResult SendKeyPress(TrackedElement* element, |
| KeyboardCode key, |
| int flags); |
| |
| #endif // !BUILDFLAG(IS_IOS) |
| |
| // Sends a "confirm" input to `element`, e.g. a RETURN keypress. |
| ActionResult Confirm(TrackedElement* element); |
| |
| private: |
| // The list of known simulators. |
| std::vector<std::unique_ptr<Simulator>> 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_ |