// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_TEST_BASE_ASH_INTERACTIVE_INTERACTIVE_ASH_TEST_H_
#define CHROME_TEST_BASE_ASH_INTERACTIVE_INTERACTIVE_ASH_TEST_H_
#include <memory>
#include "ash/webui/system_apps/public/system_web_app_type.h"
#include "base/memory/weak_ptr.h"
#include "build/chromeos_buildflags.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "chrome/test/interaction/interactive_browser_test.h"
#include "chromeos/ash/components/network/network_type_pattern.h"
#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
#include "content/public/test/browser_test.h"
#include "ui/base/interaction/interaction_sequence.h"
class GURL;
class Profile;
namespace content {
class NavigationHandle;
}
// Sets up Kombucha for ash testing:
// - Provides 1 Kombucha "context" per display, shared by all views::Widgets
// - Provides a default "context widget" so Kombucha can synthesize mouse events
// - Suppresses creating a browser window on startup, because most ash-chrome
// tests don't need the window and creating it slows down the test
//
// Because this class derives from InProcessBrowserTest the source files must be
// added to a target that defines HAS_OUT_OF_PROC_TEST_RUNNER. The source files
// cannot be in a shared test support target that lacks that define.
//
// For tests that run on a DUT or in a VM, use the subclass AshIntegrationTest,
// which supports running on hardware.
class InteractiveAshTest
: public InteractiveBrowserTestT<MixinBasedInProcessBrowserTest> {
public:
// Helper struct for filling out the Wi-Fi configuration dialog.
struct WifiDialogConfig {
std::string ssid = "";
::chromeos::network_config::mojom::SecurityType security_type =
::chromeos::network_config::mojom::SecurityType::kNone;
bool is_shared = true;
};
InteractiveAshTest();
InteractiveAshTest(const InteractiveAshTest&) = delete;
InteractiveAshTest& operator=(const InteractiveAshTest&) = delete;
~InteractiveAshTest() override;
// Sets up a context widget for Kombucha. Call this at the start of each test
// body. This is needed because InteractiveAshTest doesn't open a browser
// window by default, but Kombucha needs a widget to simulate mouse events.
void SetupContextWidget();
// Installs system web apps (SWAs) like OS Settings, Files, etc. Can be called
// in SetUpOnMainThread() or in your test body. SWAs are not installed by
// default because this speeds up tests that don't need the apps.
void InstallSystemApps();
// Launches the system web app of type `type`. Associates `element_id` with
// the app window and returns a Kombucha context for the app window.
ui::ElementContext LaunchSystemWebApp(
ash::SystemWebAppType type,
const ui::ElementIdentifier& element_id);
// Finds the system web app of type `type` and returns the Kombucha context
// for the app window.
ui::ElementContext FindSystemWebApp(ash::SystemWebAppType type);
// Attempts to close the system web app of type `type`.
void CloseSystemWebApp(ash::SystemWebAppType type);
// Navigates the Settings app, which is expected to be associated with
// `element_id`, to the top-level internet page.
ui::test::internal::InteractiveTestPrivate::MultiStep
NavigateSettingsToInternetPage(const ui::ElementIdentifier& element_id);
// Navigates the Settings app, which is expected to be associated with
// `element_id`, to the top-level bluetooth page.
ui::test::internal::InteractiveTestPrivate::MultiStep
NavigateSettingsToBluetoothPage(const ui::ElementIdentifier& element_id);
// Navigates the Settings app, which is expected to be associated with
// `element_id`, to the subpage of the network with type `network_pattern`.
ui::test::internal::InteractiveTestPrivate::MultiStep
NavigateSettingsToNetworkSubpage(
const ui::ElementIdentifier& element_id,
const ash::NetworkTypePattern network_pattern);
// Navigates the Settings app, which is expected to be associated with
// `element_id`, to the details page for the network named `network_name`
// with type `network_pattern`.
ui::test::internal::InteractiveTestPrivate::MultiStep
NavigateToInternetDetailsPage(const ui::ElementIdentifier& element_id,
const ash::NetworkTypePattern network_pattern,
const std::string& network_name);
// Navigates the Settings app, which is expected to be associated with
// `element_id`, to the Bluetooth details page for the device named
// `device_name`.
ui::test::internal::InteractiveTestPrivate::MultiStep
NavigateToBluetoothDeviceDetailsPage(const ui::ElementIdentifier& element_id,
const std::string& device_name);
// This function expects the Settings app to already be open and on the
// detailed page of a cellular network.
ui::test::internal::InteractiveTestPrivate::MultiStep
NavigateToApnRevampDetailsPage(const ui::ElementIdentifier& element_id);
// Navigates the Settings app to the Known Networks subpage.
ui::test::internal::InteractiveTestPrivate::MultiStep
NavigateToKnownNetworksPage(const ui::ElementIdentifier& element_id);
// Navigates the Settings app to the passpoint subscription details page for
// the passpoint named `passpoint_name`.
ui::test::internal::InteractiveTestPrivate::MultiStep
NavigateToPasspointSubscriptionSubpage(
const ui::ElementIdentifier& element_id,
const std::string& passpoint_name);
// This function expects the Settings app to already be open and on the APN
// subpage.
ui::test::internal::InteractiveTestPrivate::MultiStep
OpenAddCustomApnDetailsDialog(const ui::ElementIdentifier& element_id);
// This function expects the Settings app to already be open and on the APN
// subpage.
ui::test::internal::InteractiveTestPrivate::MultiStep OpenApnSelectionDialog(
const ui::ElementIdentifier& element_id);
// Open up the "Add built-in VPN" dialog. This function expects the Settings
// app to already be open.
ui::test::internal::InteractiveTestPrivate::MultiStep OpenAddBuiltInVpnDialog(
const ui::ElementIdentifier& element_id);
// Open up the "Add Wi-Fi" dialog. This function expects the Settings app to
// already be open.
ui::test::internal::InteractiveTestPrivate::MultiStep OpenAddWifiDialog(
const ui::ElementIdentifier& element_id);
// Completes the "Add Wi-Fi" dialog according to the properties provided by
// the `config` parameter. This function expects the dialog to already be open
// prior to being called.
ui::test::internal::InteractiveTestPrivate::MultiStep CompleteAddWifiDialog(
const ui::ElementIdentifier& element_id,
const WifiDialogConfig& config);
// Opens the Quick Settings bubble.
ui::test::internal::InteractiveTestPrivate::MultiStep OpenQuickSettings();
// Navigates to the internet page within Quick Settings. This function expects
// the Quick Settings to already be open and on the root page.
ui::test::internal::InteractiveTestPrivate::MultiStep
NavigateQuickSettingsToNetworkPage();
// Navigates to the bluetooth page within Quick Settings. This function
// expects the Quick Settings to already be open and on the root page.
ui::test::internal::InteractiveTestPrivate::MultiStep
NavigateQuickSettingsToHotspotPage();
// Navigates to the bluetooth page within Quick Settings. This function
// expects the Quick Settings to already be open and on the root page.
ui::test::internal::InteractiveTestPrivate::MultiStep
NavigateQuickSettingsToBluetoothPage();
// Returns the active user profile.
Profile* GetActiveUserProfile();
// Convenience method to create a new browser window at `url` for the active
// user profile. Returns the `NavigationHandle` for the started navigation,
// which might be null if the navigation couldn't be started. Tests requiring
// more complex browser setup should use `Navigate()` from
// browser_navigator.h.
base::WeakPtr<content::NavigationHandle> CreateBrowserWindow(const GURL& url);
// MixinBasedInProcessBrowserTest:
void TearDownOnMainThread() override;
// Blocks until a window exists with the given title. If a matching window
// already exists the test will resume immediately.
ui::test::internal::InteractiveTestPrivate::MultiStep WaitForWindowWithTitle(
aura::Env* env,
std::u16string title);
// Waits for an element identified by `query` to exist in the DOM of an
// instrumented WebUI identified by `element_id`.
ui::test::internal::InteractiveTestPrivate::MultiStep WaitForElementExists(
const ui::ElementIdentifier& element_id,
const WebContentsInteractionTestUtil::DeepQuery& query);
// Waits for an element identified by `query` to not exist in the DOM of an
// instrumented WebUI identified by `element_id`.
ui::test::internal::InteractiveTestPrivate::MultiStep
WaitForElementDoesNotExist(
const ui::ElementIdentifier& element_id,
const WebContentsInteractionTestUtil::DeepQuery& query);
// Waits for an element identified by `query` to both exist in the DOM of an
// instrumented WebUI identified by `element_id` and be enabled.
InteractiveTestApi::MultiStep WaitForElementEnabled(
const ui::ElementIdentifier& element_id,
WebContentsInteractionTestUtil::DeepQuery query);
// Waits for an element identified by `query` to both exist in the DOM of an
// instrumented WebUI identified by `element_id` and have a particular value
// for a boolean property within its managed properties. Managed properties
// are used to communicate the state of networks to the UI.
ui::test::internal::InteractiveTestPrivate::MultiStep
WaitForElementWithManagedPropertyBoolean(
const ui::ElementIdentifier& element_id,
const WebContentsInteractionTestUtil::DeepQuery& query,
const std::string& property,
bool expected_value);
// Waits for an element identified by `query` to both exist in the DOM of an
// instrumented WebUI identified by `element_id` and be disabled.
InteractiveTestApi::MultiStep WaitForElementDisabled(
const ui::ElementIdentifier& element_id,
WebContentsInteractionTestUtil::DeepQuery query);
// Waits for an element identified by `query` to both exist in the DOM of an
// instrumented WebUI identified by `element_id` and be checked.
InteractiveTestApi::MultiStep WaitForElementChecked(
const ui::ElementIdentifier& element_id,
WebContentsInteractionTestUtil::DeepQuery query);
// Waits for an element identified by `query` to both exist in the DOM of an
// instrumented WebUI identified by `element_id` and be unchecked.
InteractiveTestApi::MultiStep WaitForElementUnchecked(
const ui::ElementIdentifier& element_id,
WebContentsInteractionTestUtil::DeepQuery query);
// Waits for an element identified by `query` to both exist in the DOM of an
// instrumented WebUI identified by `element_id` and be expanded.
InteractiveTestApi::MultiStep WaitForElementExpanded(
const ui::ElementIdentifier& element_id,
WebContentsInteractionTestUtil::DeepQuery query);
// Waits for an element identified by `query` to both exist in the DOM of an
// instrumented WebUI identified by `element_id` and be opened.
InteractiveTestApi::MultiStep WaitForElementOpened(
const ui::ElementIdentifier& element_id,
WebContentsInteractionTestUtil::DeepQuery query);
// Waits for an element identified by `query` to both exist in the DOM of an
// instrumented WebUI identified by `element_id` and be unopened.
InteractiveTestApi::MultiStep WaitForElementUnopened(
const ui::ElementIdentifier& element_id,
WebContentsInteractionTestUtil::DeepQuery query);
// Waits for an element identified by `query` to exist in the DOM of an
// instrumented WebUI identified by `element_id` and be focused.
ui::test::internal::InteractiveTestPrivate::MultiStep WaitForElementFocused(
const ui::ElementIdentifier& element_id,
const WebContentsInteractionTestUtil::DeepQuery& query);
// Waits for an element identified by `query` to both exist in the DOM of an
// instrumented WebUI identified by `element_id` and have its text, or the
// text of any of its children, match `expected`.
ui::test::internal::InteractiveTestPrivate::MultiStep
WaitForElementTextContains(
const ui::ElementIdentifier& element_id,
const WebContentsInteractionTestUtil::DeepQuery& query,
const std::string& expected);
// This function is similar to `WaitForElementTextContains()` except it
// supports non-unique elements. For more info see
// `FindElementAndDoActionOnChildren()`.
ui::test::internal::InteractiveTestPrivate::MultiStep
WaitForAnyElementTextContains(
const ui::ElementIdentifier& element_id,
const WebContentsInteractionTestUtil::DeepQuery& root,
const WebContentsInteractionTestUtil::DeepQuery& selectors,
const std::string& expected);
// Waits for an element identified by `query` to both exist in the DOM of an
// instrumented WebUI identified by `element_id` and have attribute
// `attribute`.
InteractiveTestApi::MultiStep WaitForElementHasAttribute(
const ui::ElementIdentifier& element_id,
WebContentsInteractionTestUtil::DeepQuery element,
const std::string& attribute);
// Waits for an element identified by `query` to both exist in the DOM of an
// instrumented WebUI identified by `element_id` and have a display style of
// `none`, indicating that the element is not visible.
InteractiveTestApi::MultiStep WaitForElementDisplayNone(
const ui::ElementIdentifier& element_id,
WebContentsInteractionTestUtil::DeepQuery element);
// Waits for a toggle element identified by `query` to both exist in the DOM
// of an instrumented WebUI identified by `element_id` and to be toggled .
ui::test::internal::InteractiveTestPrivate::MultiStep WaitForToggleState(
const ui::ElementIdentifier& element_id,
const WebContentsInteractionTestUtil::DeepQuery& query,
bool is_checked);
// Clears the text value of an input element identified by `query` in
// the DOM of an instrumented WebUI identified by `element_id` .
ui::test::internal::InteractiveTestPrivate::MultiStep ClearInputFieldValue(
const ui::ElementIdentifier& element_id,
const WebContentsInteractionTestUtil::DeepQuery& query);
// Waits for an element to render by using `getBoundingClientRect()` to verify
// the element is visible and ready for interactions. Helps to prevent
// `element_bounds.IsEmpty()` flakes.
ui::test::internal::InteractiveTestPrivate::MultiStep WaitForElementToRender(
const ui::ElementIdentifier& element_id,
const WebContentsInteractionTestUtil::DeepQuery& query);
// Clicks on an element in the DOM. `element_id` is the identifier
// of the WebContents to query. `query` is a
// WebContentsInteractionTestUtil::DeepQuery path to the element to start
// with, it can be {} to query the entire page.
ui::test::internal::InteractiveTestPrivate::MultiStep ClickElement(
const ui::ElementIdentifier& element_id,
const WebContentsInteractionTestUtil::DeepQuery& query);
// This function is similar to `ClickElement()` except it supports non-unique
// elements. For more info see `FindElementAndDoActionOnChildren()`.
ui::test::internal::InteractiveTestPrivate::MultiStep
ClickAnyElementTextContains(
const ui::ElementIdentifier& element_id,
const WebContentsInteractionTestUtil::DeepQuery& root,
const WebContentsInteractionTestUtil::DeepQuery& selectors,
const std::string& expected);
// Waits for an element identified by `query` to exist in the DOM of an
// instrumented WebUI identified by `element_id`. This function expects the
// element to be a drop-down and will directly update the selected option
// index to match the first option matching `option`.
ui::test::internal::InteractiveTestPrivate::MultiStep
SelectDropdownElementOption(
const ui::ElementIdentifier& element_id,
const WebContentsInteractionTestUtil::DeepQuery& query,
const std::string& option);
// Sends an instrumented WebUI identified by `element_id` the key presses
// needed to input the provided text `text`. This function can handle ASCII
// letters, numbers, the newline character, and a subset of symbols.
// TODO(crbug.com/40286410) have a more supported way to do this and remove
// this function.
ui::test::internal::InteractiveTestPrivate::MultiStep SendTextAsKeyEvents(
const ui::ElementIdentifier& element_id,
const std::string& text);
// Waits for an element identified by `query` to exist in the DOM of an
// instrumented WebUI identified by `element_id`, which is expected to be a
// text input field, clears the existing input, clicks the element, and sends
// the key events needed to type `text`.
ui::test::internal::InteractiveTestPrivate::MultiStep ClearInputAndEnterText(
const ui::ElementIdentifier& element_id,
const WebContentsInteractionTestUtil::DeepQuery& query,
const std::string& text);
// Waits for an element identified by `root` to exist in the DOM of an
// instrumented WebUI identified by `element_id`. When found, this function
// will search for its children elements by `selectors` and will execute
// `action` on each element until `action` returns a truthy value.
ui::test::internal::InteractiveTestPrivate::MultiStep
FindElementAndDoActionOnChildren(
const ui::ElementIdentifier& element_id,
const WebContentsInteractionTestUtil::DeepQuery& root,
const WebContentsInteractionTestUtil::DeepQuery& selectors,
const std::string& action);
private:
// Helper function that navigates to a top-level page of the Settings app.
// This function expects the Settings app to already be open. The `path`
// parameter should correspond to a top-level menu item.
ui::test::internal::InteractiveTestPrivate::MultiStep NavigateSettingsToPage(
const ui::ElementIdentifier& element_id,
const char* path);
// Helper function that navigates to a detailed page within Quick Settings.
// This function expects the Quick Settings to already be open and on the root
// page. The `element_id` parameter should be the drill-in arrow for the
// desired detailed page.
ui::test::internal::InteractiveTestPrivate::MultiStep
NavigateQuickSettingsToPage(const ui::ElementIdentifier& element_id);
// Returns the JS code that searches for an element selected by
// `element_with_text` that contains the expected text, and when found will
// click on the sibling element selected by `element_to_click`.
const std::string ClickElementWithSiblingContainsText(
const WebContentsInteractionTestUtil::DeepQuery& element_with_text,
const std::string& expected,
const WebContentsInteractionTestUtil::DeepQuery& element_to_click);
};
#endif // CHROME_TEST_BASE_ASH_INTERACTIVE_INTERACTIVE_ASH_TEST_H_