blob: 5289927cf0dc78f628d998ec5d66675afb33d7bb [file] [log] [blame]
// Copyright 2012 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_UI_TEST_UTILS_H_
#define CHROME_TEST_BASE_UI_TEST_UTILS_H_
#include <set>
#include <string>
#include <string_view>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/browser/ui/exclusive_access/fullscreen_observer.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "chrome/browser/ui/view_ids.h"
#include "components/history/core/browser/history_service.h"
#include "content/public/test/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/window_open_disposition.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/views/view_observer.h"
#include "url/gurl.h"
#if defined(TOOLKIT_VIEWS)
#include "ui/views/test/widget_test_api.h"
#endif
class Browser;
class BrowserList;
class BrowserWindowInterface;
class FullscreenController;
class Profile;
namespace javascript_dialogs {
class AppModalDialogController;
}
namespace base {
class FilePath;
}
struct NavigateParams;
namespace content {
class RenderFrameHost;
class WebContents;
}
namespace gfx {
class Rect;
}
namespace views {
class View;
}
// A collections of functions designed for use with InProcessBrowserTest.
namespace ui_test_utils {
// Flags to indicate what to wait for in a navigation test.
// They can be ORed together.
// The order in which the waits happen when more than one is selected, is:
// Browser
// Tab
// Navigation
enum BrowserTestWaitFlags {
// Don't wait for anything.
BROWSER_TEST_NO_WAIT = 0,
// Wait for a new browser.
BROWSER_TEST_WAIT_FOR_BROWSER = 1 << 0,
// Wait for a new tab.
BROWSER_TEST_WAIT_FOR_TAB = 1 << 1,
// Wait for loading to stop. Loading stops when either
// a document and its subresources are completely loaded
// (i.e. the spinner has stopped) or no document can be
// loaded due to an e.g. an error or crash.
BROWSER_TEST_WAIT_FOR_LOAD_STOP = 1 << 2,
BROWSER_TEST_MASK = BROWSER_TEST_WAIT_FOR_BROWSER |
BROWSER_TEST_WAIT_FOR_TAB |
BROWSER_TEST_WAIT_FOR_LOAD_STOP
};
// Puts the current tab title in |title|. Returns true on success.
bool GetCurrentTabTitle(const BrowserWindowInterface* browser,
std::u16string* title);
// NavigateToURL* functions navigate the given |browser| to |url| according the
// provided parameters and block until ready (by default - until loading stops,
// see BROWSER_TEST_WAIT_FOR_LOAD_STOP for more details. Note that this is
// different from content::NavigateToURL, which block only until navigation
// succeeds or fails, which generally happens earlier).
//
// Some of these functions return RenderFrameHost* where the navigation was
// committed or nullptr if the navigation failed. The caller should inspect the
// return value - typically with: ASSERT_TRUE(NavigateToURL(...)).
//
// Note: if the navigation has committed, this doesn't mean that the old
// RenderFrameHost was destroyed:
// - it either can wait for the renderer process to finish running unload
// handlers and acknowledge that.
// - it can be stored in BackForwardCache to be reused for subsequent
// back/forward navigation.
//
// If the test needs to test RenderFrameHost cleanup, use
// BackForwardCache::DisableForTesting to ensure that RenderFrameHost isn't
// preserved in BackForwardCache and
// RenderFrameDeletedObserver::WaitUntilDeleted to wait for deletion.
// Navigate according to |params|.
void NavigateToURL(NavigateParams* params);
// Navigate current tab of the |browser| to |url| using POST request, simulating
// form submission.
void NavigateToURLWithPost(BrowserWindowInterface* browser, const GURL& url);
// Navigate current tab of the |browser| to |url|, simulating a user typing
// |url| into the omnibox.
[[nodiscard]] content::RenderFrameHost* NavigateToURL(
BrowserWindowInterface* browser,
const GURL& url);
// Same as |NavigateToURL|, but:
// - |disposition| allows to specify in which tab navigation should happen
// - |browser_test_flags| allows to specify a different condition this function
// would wait until, see BrowserTestWaitFlags for details.
content::RenderFrameHost* NavigateToURLWithDisposition(
BrowserWindowInterface* browser,
const GURL& url,
WindowOpenDisposition disposition,
int browser_test_flags);
// Same as |NavigateToURL|, but wait for a given number of navigations to
// complete instead of the tab to finish loading.
content::RenderFrameHost* NavigateToURLBlockUntilNavigationsComplete(
BrowserWindowInterface* browser,
const GURL& url,
int number_of_navigations);
// See |NavigateToURLWithDisposition| and
// |NavigateToURLBlockUntilNavigationsComplete|.
content::RenderFrameHost*
NavigateToURLWithDispositionBlockUntilNavigationsComplete(
BrowserWindowInterface* browser,
const GURL& url,
int number_of_navigations,
WindowOpenDisposition disposition,
int browser_test_flags);
// Generate the path of the build directory, relative to the source root.
bool GetRelativeBuildDirectory(base::FilePath* build_dir);
// Blocks until an application modal dialog is shown and returns it.
javascript_dialogs::AppModalDialogController* WaitForAppModalDialog();
// Blocks until the web contents shows a web modal dialog.
void WaitForWebModalDialog(content::WebContents* web_contents);
#if defined(TOOLKIT_VIEWS)
// Blocks until the given view attains the given visibility state.
void WaitForViewVisibility(BrowserWindowInterface* browser,
ViewID vid,
bool visible);
#endif
// Performs a find in the page of the specified tab. Returns the number of
// matches found. |ordinal| is an optional parameter which is set to the index
// of the current match. |selection_rect| is an optional parameter which is set
// to the location of the current match.
int FindInPage(content::WebContents* tab,
const std::u16string& search_string,
bool forward,
bool case_sensitive,
int* ordinal,
gfx::Rect* selection_rect);
// Blocks until the |history_service|'s history finishes loading.
void WaitForHistoryToLoad(history::HistoryService* history_service);
// Blocks until a Browser is added to the BrowserList.
Browser* WaitForBrowserToOpen();
// Blocks until a Browser is removed from the BrowserList. If |browser| is null,
// the removal of any browser will suffice; otherwise the removed browser must
// match |browser|.
void WaitForBrowserToClose(BrowserWindowInterface* browser = nullptr);
// Download the given file and waits for the download to complete.
void DownloadURL(BrowserWindowInterface* browser, const GURL& download_url);
// Waits until the autocomplete controller reaches its done state.
void WaitForAutocompleteDone(BrowserWindowInterface* browser);
// Waits until the window gets minimized.
// Returns success or not.
bool WaitForMinimized(BrowserWindowInterface* browser);
// Waits until the window gets maximized.
// Returns success or not.
bool WaitForMaximized(BrowserWindowInterface* browser);
// See comment on views::AsyncWidgetRequestWaiter.
[[nodiscard]] views::AsyncWidgetRequestWaiter CreateAsyncWidgetRequestWaiter(
BrowserWindowInterface& browser);
// SetAndWaitForBounds sets the given `bounds` on `browser` and waits until the
// bounds update will be observable from all parts of the client (on Wayland).
// This does not verify the resulting bounds.
void SetAndWaitForBounds(BrowserWindowInterface& browser,
const gfx::Rect& bounds);
// Maximizes the browser window and wait until the window is maximized and all
// related visible UI effects are applied and observable from chrome.
// Returns true if succeeded.
bool MaximizeAndWaitUntilUIUpdateDone(BrowserWindowInterface& browser);
// Waits for fullscreen state to be updated.
// There're two variation of fullscreen concepts, browser fullscreen and
// tab fullscreen. Due to fullscreen implementation, fullscreen state may
// be updated synchronously, while observer invocations and some other
// following tasks are done asynchronously.
// This class checks the condition on instance creation, then every
// OnFullscreenStateChanged invocation to deal with the situation.
// Once the condition is met, this class remembers the state, so following
// Wait() will do nothing, even if the condition is changed once again.
class FullscreenWaiter : public FullscreenObserver {
public:
// The conditions to be satisfied. std::nullopt means to ignore the
// value.
struct Expectation {
// Condition for IsFullscreenForBrowser() to satisfy.
std::optional<bool> browser_fullscreen;
// Condition for IsTabFullscreen() to satisfy.
std::optional<bool> tab_fullscreen;
// ID of the display to be used for the fullscreen.
std::optional<int64_t> display_id;
};
// Shortcut constant representing no fullscreen is enabled.
inline static constexpr Expectation kNoFullscreen = {
.browser_fullscreen = false,
.tab_fullscreen = false,
};
FullscreenWaiter(BrowserWindowInterface* browser, Expectation expecation);
FullscreenWaiter(const FullscreenWaiter&) = delete;
FullscreenWaiter& operator=(const FullscreenWaiter&) = delete;
~FullscreenWaiter() override;
// Waits for the fullscreen state(s) to be satisfied.
// Once it is satisfied after creation, this will do nothing,
// even if the state is changed once again, and does not satisfy
// the condition on calling Wait().
void Wait();
// FullscreenObserver:
void OnFullscreenStateChanged() override;
private:
// Checks whether the condition is satisfied now.
bool IsSatisfied() const;
const Expectation expectation_;
const raw_ptr<FullscreenController> controller_;
base::ScopedObservation<FullscreenController, FullscreenObserver>
observation_{this};
base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
// Caches if the condition is satisfied even once.
bool satisfied_;
};
// This waiter waits for the specified |browser| becoming the last active
// browser in BrowserList. In Lacros, BrowserList::SetLastActive is triggered by
// OnWidgetActivationChanged when wayland notify the UI change asynchronously.
// Many testing code needs to wait until the expected browser to be set as
// the last active browser, and some testing code needs to wait until
// BrowserList::OnSetLastActive() is observed.
class BrowserDidBecomeActiveWaiter {
public:
// By default, the waiting will be satisfied if the expected |browser| is the
// last active browser in BrowserList. In most cases, the testing code
// depending on chrome::FindLastActive() should be good.
// In some cases, for example, when there is only one browser in the
// BrowserList, |browser| can be returned as the last active browser even if
// the asynchronous Wayland UI event has not arrived yet (i.e.
// BrowserList::SetLastActive() is not triggered and the code observing
// BrowserList::OnSetLastActive() will not be called). If the test case
// depends on the code observing BrowserList::OnSetLastActive() being executed
// first, we can configure the waiter to be satisfied upon
// OnBrowserSetLastActive is observed by passing
// |wait_for_set_last_active_observed| being true.
explicit BrowserDidBecomeActiveWaiter(
BrowserWindowInterface* browser,
bool wait_for_set_last_active_observed = false);
BrowserDidBecomeActiveWaiter(const BrowserDidBecomeActiveWaiter&) = delete;
BrowserDidBecomeActiveWaiter& operator=(const BrowserDidBecomeActiveWaiter&) =
delete;
~BrowserDidBecomeActiveWaiter();
// Runs a loop until |browser_| becomes the last active browser.
void Wait();
void OnBrowserDidBecomeActive(BrowserWindowInterface* Browser);
private:
base::CallbackListSubscription browser_did_become_active_subscription_;
bool satisfied_ = false;
bool wait_for_set_last_active_observed_ = false;
base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
};
// Toggles browser fullscreen mode, then wait for its completion.
void ToggleFullscreenModeAndWait(BrowserWindowInterface* browser);
// Waits until |browser| becomes active.
void WaitUntilBrowserBecomeActive(BrowserWindowInterface* browser);
// Returns true if |browser| is active.
bool IsBrowserActive(BrowserWindowInterface* browser);
// Opens a new browser window with chrome::NewEmptyWindow() and wait until it
// becomes active.
// Returns newly created browser.
Browser* OpenNewEmptyWindowAndWaitUntilActivated(
Profile* profile,
bool should_trigger_session_restore = false);
// Waits for |browser| becomes the last active browser.
// By default, the waiting will be satisfied if the expected |browser| is the
// last active browser in BrowserList. In most cases, this is enough for the
// testing code depending on chrome::FindLastActive(). In some cases, for
// example, when there is only one browser in the BrowserList, |browser| can be
// returned as the last active browser even if the asynchronous Wayland UI event
// has not arrived yet (i.e. BrowserList::SetLastActive() is not triggered and
// the code observing BrowserList::OnSetLastActive() will not be called). If the
// test case depends on the code observing BrowserList::OnSetLastActive() being
// executed first, we can configure the waiter to be satisfied upon
// OnBrowserSetLastActive is observed by passing
// |wait_for_set_last_active_observed| being true.
// Note: The last active browser is not necessarily the current active browser.
// A browser could be de-activated and still the last active browser. In many
// tests, BrowserList::GetLastActive() is incorrectly used to verify the
// expected browser being the active browser, see b/345848530.
void WaitForBrowserSetLastActive(
BrowserWindowInterface* browser,
bool wait_for_set_last_active_observed = false);
// Send the given text to the omnibox and wait until it's updated.
void SendToOmniboxAndSubmit(
BrowserWindowInterface* browser,
std::string_view input,
base::TimeTicks match_selection_timestamp = base::TimeTicks());
// Gets the first browser that is not in the specified set.
Browser* GetBrowserNotInSet(
const std::set<BrowserWindowInterface*>& excluded_browsers);
// Returns a list of browsers for which `matcher` returns true.
using BrowserMatcher = base::FunctionRef<bool(BrowserWindowInterface*)>;
std::vector<BrowserWindowInterface*> FindMatchingBrowsers(
BrowserMatcher matcher);
// Gets the size and value of the cookie string for |url| in the given tab.
// Can be called from any thread.
void GetCookies(const GURL& url,
content::WebContents* contents,
int* value_size,
std::string* value);
// Utility class to watch all existing and added tabs, until some interesting
// thing has happened. Subclasses get to decide what they consider to be
// interesting. In practice, usage is like this:
//
// - Subclass `AllTabsObserver`
// - Override `ProcessOneWebContents()` to check for the interesting thing.
// - Optionally return a `WebContentsObserver` that will watch for the
// interesting thing for this WebContents.
// - Eventually call `ConditionMet()` to indicate that the interesting thing has
// happened, and no further waiting is needed.
//
// Users of this class just call `Wait()` at most once.
class AllTabsObserver : public TabStripModelObserver,
public BrowserListObserver {
public:
AllTabsObserver(const AllTabsObserver&) = delete;
AllTabsObserver& operator=(const AllTabsObserver&) = delete;
~AllTabsObserver() override;
// Waits until whatever interesting thing we're waiting for has happened.
// Will return immediately if it's already happened.
void Wait();
protected:
AllTabsObserver();
// Will be called for every tab's WebContents, including ones that exist when
// this class is constructed and any that are created afterwards until
// destruction or until `ConditionMet()` is called.
//
// This may choose not to return an observer if there's no need to watch this
// contents. It may also call `ConditionMet()` before returning, presumably
// because the new contents already matches whatever condition our subclass is
// checking for. In that case, it will presumably not bother to return an
// observer for the new contents.
//
// These will be deleted before `this` is deleted, so it's okay to have the
// observers hold raw_ptrs back to `this`.
virtual std::unique_ptr<base::CheckedObserver> ProcessOneContents(
content::WebContents* web_contents) = 0;
// Add all tabs from all browsers. Must be called by the subclass ctor.
void AddAllBrowsers();
// Called by our subclass to let us know that whatever it's trying to wait for
// has happened. May be called at any time, including during a call to
// `CreateObserverIfNeeded()`. May be called more than once, though
// calls will be ignored.
void ConditionMet();
private:
// Record for every tab we're watching.
struct TabNavigationMapEntry {
TabNavigationMapEntry();
~TabNavigationMapEntry();
// Provided by the subclass to do whatever it does.
std::unique_ptr<base::CheckedObserver> subclass_observer;
// Provided by us to clean up properly.
std::unique_ptr<base::CheckedObserver> destruction_observer;
};
using TabNavigationMap =
std::map<const content::WebContents*, TabNavigationMapEntry>;
// Add all tabs from `browser`, and start watching for changes.
void AddBrowser(BrowserWindowInterface* browser);
// TabStripModelObserver:
void OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) override;
// BrowserListObserver:
void OnBrowserAdded(Browser* browser) override;
// Called for every WebContents. Notifies the subclass, and sets up observers
// if needed.
void AddWebContents(content::WebContents* web_contents);
// Called by our destruction observers.
void OnWebContentsDestroyed(content::WebContents* web_contents);
// Map of how many times each tab has navigated since |this| was created.
TabNavigationMap tab_navigation_map_;
// True if WaitForNavigations has been called, until
// |num_navigations_to_wait_for_| have been observed.
bool condition_met_ = false;
// Flag to make sure that subclasses call `AddAllBrowsers()`.
bool added_all_browsers_ = false;
std::unique_ptr<base::RunLoop> run_loop_;
base::ScopedObservation<BrowserList, BrowserListObserver>
browser_list_observation_{this};
};
// Observer which waits for navigation events and blocks until a specific URL is
// loaded. The URL must be an exact match.
class UrlLoadObserver : public AllTabsObserver {
public:
// `url` is the URL to look for.
explicit UrlLoadObserver(const GURL& url);
~UrlLoadObserver() override;
// Returns the WebContents which navigated to `url`.
content::WebContents* web_contents() const { return web_contents_; }
protected:
// Helper class to watch for DidStopLoading on one WebContents and relay it to
// the UrlLoadObserver that created us.
class LoadStopObserver : public content::WebContentsObserver {
public:
LoadStopObserver(UrlLoadObserver* owner,
content::WebContents* web_contents);
~LoadStopObserver() override;
// WebContentsObserver
void DidStopLoading() override;
private:
raw_ptr<UrlLoadObserver> owner_ = nullptr;
};
// AllTabsObserver
std::unique_ptr<base::CheckedObserver> ProcessOneContents(
content::WebContents* web_contents) override;
// Called by `LoadStopObserver` when a WebContents DidStopLoading().
void OnDidStopLoading(content::WebContents* web_contents);
private:
friend class LoadStopObserver;
GURL url_;
raw_ptr<content::WebContents> web_contents_ = nullptr;
};
// A helper that will wait until a tab is added to a specific Browser.
class TabAddedWaiter : public TabStripModelObserver {
public:
explicit TabAddedWaiter(BrowserWindowInterface* browser);
TabAddedWaiter(const TabAddedWaiter&) = delete;
TabAddedWaiter& operator=(const TabAddedWaiter&) = delete;
~TabAddedWaiter() override = default;
content::WebContents* Wait();
// TabStripModelObserver:
void OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) override;
private:
base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
raw_ptr<content::WebContents, AcrossTasksDanglingUntriaged> web_contents_ =
nullptr;
};
// Similar to TabAddedWaiter, but will observe tabs added to all Browser
// objects, and can return the last tab that was added.
class AllBrowserTabAddedWaiter : public TabStripModelObserver,
public BrowserListObserver {
public:
AllBrowserTabAddedWaiter();
AllBrowserTabAddedWaiter(const AllBrowserTabAddedWaiter&) = delete;
AllBrowserTabAddedWaiter& operator=(const AllBrowserTabAddedWaiter&) = delete;
~AllBrowserTabAddedWaiter() override;
content::WebContents* Wait();
// TabStripModelObserver:
void OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) override;
// BrowserListObserver:
void OnBrowserAdded(Browser* browser) override;
private:
base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
// The last tab that was added.
raw_ptr<content::WebContents, AcrossTasksDanglingUntriaged> web_contents_ =
nullptr;
base::ScopedObservation<BrowserList, BrowserListObserver>
browser_list_observation_{this};
};
// Enumerates all history contents on the backend thread. Returns them in
// descending order by time.
class HistoryEnumerator {
public:
explicit HistoryEnumerator(Profile* profile);
HistoryEnumerator(const HistoryEnumerator&) = delete;
HistoryEnumerator& operator=(const HistoryEnumerator&) = delete;
~HistoryEnumerator();
std::vector<GURL>& urls() { return urls_; }
private:
std::vector<GURL> urls_;
};
// Waits for the destruction of `browser`. If `browser` is null will wait on the
// destruction of any Browser.
class BrowserDestroyedObserver : public BrowserListObserver {
public:
explicit BrowserDestroyedObserver(BrowserWindowInterface* browser = nullptr);
BrowserDestroyedObserver(const BrowserDestroyedObserver&) = delete;
BrowserDestroyedObserver& operator=(const BrowserDestroyedObserver&) = delete;
~BrowserDestroyedObserver() override;
void Wait();
// BrowserListObserver:
void OnBrowserRemoved(Browser* browser) override;
private:
bool was_removed_ = false;
const std::optional<SessionID> session_id_;
base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
base::ScopedObservation<BrowserList, BrowserListObserver>
browser_list_observation_{this};
};
// Waits for the creation of `browser`. If `browser` is null will wait on the
// creation of any Browser.
class BrowserCreatedObserver : public BrowserListObserver {
public:
BrowserCreatedObserver();
BrowserCreatedObserver(const BrowserCreatedObserver&) = delete;
BrowserCreatedObserver& operator=(const BrowserCreatedObserver&) = delete;
~BrowserCreatedObserver() override;
Browser* Wait();
// BrowserListObserver:
void OnBrowserAdded(Browser* browser) override;
void OnBrowserRemoved(Browser* browser) override;
private:
raw_ptr<Browser> browser_ = nullptr;
base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
base::ScopedObservation<BrowserList, BrowserListObserver>
browser_list_observation_{this};
};
// Encapsulates waiting for the browser window to change state. This is
// needed for example on Chrome desktop linux, where window state change is done
// asynchronously as an event received from a different process.
class CheckWaiter {
public:
CheckWaiter(base::RepeatingCallback<bool()> callback,
bool expected,
const base::TimeDelta& timeout);
CheckWaiter(const CheckWaiter&) = delete;
CheckWaiter& operator=(const CheckWaiter&) = delete;
~CheckWaiter();
// Blocks until the browser window becomes maximized.
void Wait();
private:
bool Check();
base::RepeatingCallback<bool()> callback_;
bool expected_;
const base::TimeTicks timeout_;
// The waiter's RunLoop quit closure.
base::RepeatingClosure quit_;
};
// Used to wait for the view to contain non-empty bounds.
class ViewBoundsWaiter : public views::ViewObserver {
public:
explicit ViewBoundsWaiter(views::View* observed_view);
ViewBoundsWaiter(const ViewBoundsWaiter&) = delete;
ViewBoundsWaiter& operator=(const ViewBoundsWaiter&) = delete;
~ViewBoundsWaiter() override;
// Blocks until the view has non-empty bounds.
void WaitForNonEmptyBounds();
private:
// views::ViewObserver:
void OnViewBoundsChanged(views::View* observed_view) override;
bool observed_non_empty_bounds_ = false;
const raw_ptr<views::View> observed_view_;
base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
};
} // namespace ui_test_utils
#endif // CHROME_TEST_BASE_UI_TEST_UTILS_H_