blob: 1b226996e74b046ace2f837744d6b4cdb3e8bd3c [file] [log] [blame]
// Copyright 2020 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_BROWSER_UI_VIEWS_WEB_APPS_WEB_APP_INTEGRATION_TEST_DRIVER_H_
#define CHROME_BROWSER_UI_VIEWS_WEB_APPS_WEB_APP_INTEGRATION_TEST_DRIVER_H_
#include <iosfwd>
#include <memory>
#include <string>
#include <vector>
#include "base/auto_reset.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
#include "chrome/browser/web_applications/os_integration/os_integration_test_override.h"
#include "chrome/browser/web_applications/test/web_app_test_observers.h"
#include "chrome/browser/web_applications/web_app_callback_app_identity.h"
#include "chrome/browser/web_applications/web_app_id.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_ui_manager.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/run_on_os_login_types.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/views/widget/any_widget_observer.h"
#include "url/gurl.h"
class Browser;
class PageActionIconView;
namespace base {
class CommandLine;
} // namespace base
namespace web_app::integration_tests {
// Enumerations used by the integration tests framework actions. These are C++
// versions of the enumerations in the file chrome/test/webapps/data/enums.md.
enum class Site {
kStandalone,
kStandaloneNestedA,
kStandaloneNestedB,
kStandaloneNotStartUrl,
kMinimalUi,
kNotPromotable,
kWco,
kFileHandler,
kNoServiceWorker,
kNotInstalled,
kScreenshots,
kHasSubApps,
kSubApp1,
kSubApp2,
};
enum class InstallableSite {
kStandalone,
kStandaloneNestedA,
kStandaloneNestedB,
kStandaloneNotStartUrl,
kMinimalUi,
kWco,
kFileHandler,
kNoServiceWorker,
kNotInstalled,
kScreenshots,
kHasSubApps,
kSubApp1,
kSubApp2,
};
enum class Title { kStandaloneOriginal, kStandaloneUpdated };
enum class Color { kRed, kGreen };
enum class ProfileClient { kClient2, kClient1 };
enum class ProfileName { kDefault, kProfile2 };
enum class UserDisplayPreference { kStandalone, kBrowser };
enum class IsShown { kShown, kNotShown };
enum class IsOn { kOn, kOff };
enum class Display { kBrowser, kStandalone, kMinimalUi, kWco };
enum class WindowOptions { kWindowed, kBrowser };
enum class ShortcutOptions { kWithShortcut, kNoShortcut };
enum class InstallMode { kWebApp, kWebShortcut };
enum class AllowDenyOptions { kAllow, kDeny };
enum class AskAgainOptions { kAskAgain, kRemember };
enum class FileExtension { kFoo, kBar };
enum class FilesOptions {
kOneFooFile,
kMultipleFooFiles,
kOneBarFile,
kMultipleBarFiles,
kAllFooAndBarFiles
};
enum class UpdateDialogResponse {
kAcceptUpdate,
kCancelDialogAndUninstall,
kSkipUpdate
};
enum class SubAppInstallDialogOptions {
kUserAllow,
kUserDeny,
kPolicyOverride
};
// These structs are used to store the current state of the world before & after
// each state-change action.
struct TabState {
explicit TabState(GURL tab_url) : url(std::move(tab_url)) {}
TabState(const TabState&) = default;
TabState& operator=(const TabState&) = default;
bool operator==(const TabState& other) const { return url == other.url; }
GURL url;
};
struct BrowserState {
BrowserState(Browser* browser_ptr,
base::flat_map<content::WebContents*, TabState> tab_state,
content::WebContents* active_web_contents,
const AppId& app_id,
bool launch_icon_visible);
~BrowserState();
BrowserState(const BrowserState&);
bool operator==(const BrowserState& other) const;
// This field is not a raw_ptr<> because it was filtered by the rewriter for:
// #union
RAW_PTR_EXCLUSION Browser* browser;
base::flat_map<content::WebContents*, TabState> tabs;
// This field is not a raw_ptr<> because it was filtered by the rewriter for:
// #union
RAW_PTR_EXCLUSION content::WebContents* active_tab;
// If this isn't an app browser, `app_id` is empty.
AppId app_id;
bool launch_icon_shown;
};
struct AppState {
AppState(AppId app_id,
std::string app_name,
GURL app_scope,
apps::RunOnOsLoginMode run_on_os_login_mode,
blink::mojom::DisplayMode effective_display_mode,
absl::optional<mojom::UserDisplayMode> user_display_mode,
std::string manifest_launcher_icon_filename,
bool is_installed_locally,
bool is_shortcut_created);
~AppState();
AppState(const AppState&);
bool operator==(const AppState& other) const;
AppId id;
std::string name;
GURL scope;
apps::RunOnOsLoginMode run_on_os_login_mode;
blink::mojom::DisplayMode effective_display_mode;
absl::optional<mojom::UserDisplayMode> user_display_mode;
std::string manifest_launcher_icon_filename;
bool is_installed_locally;
bool is_shortcut_created;
};
struct ProfileState {
ProfileState(base::flat_map<Browser*, BrowserState> browser_state,
base::flat_map<AppId, AppState> app_state);
~ProfileState();
ProfileState(const ProfileState&);
bool operator==(const ProfileState& other) const;
base::flat_map<Browser*, BrowserState> browsers;
base::flat_map<AppId, AppState> apps;
};
struct StateSnapshot {
explicit StateSnapshot(base::flat_map<Profile*, ProfileState> profile_state);
~StateSnapshot();
StateSnapshot(const StateSnapshot&);
bool operator==(const StateSnapshot& other) const;
base::flat_map<Profile*, ProfileState> profiles;
};
std::ostream& operator<<(std::ostream& os, const StateSnapshot& snapshot);
class WebAppIntegrationTestDriver : WebAppInstallManagerObserver {
public:
class TestDelegate {
public:
// Exposing normal functionality of testing::InProcBrowserTest:
virtual Browser* CreateBrowser(Profile* profile) = 0;
virtual void CloseBrowserSynchronously(Browser* browser) = 0;
virtual void AddBlankTabAndShow(Browser* browser) = 0;
virtual const net::EmbeddedTestServer* EmbeddedTestServer() const = 0;
virtual Profile* GetDefaultProfile() = 0;
// Functionality specific to web app integration test type (e.g. sync or
// non-sync tests).
virtual bool IsSyncTest() = 0;
virtual void SyncTurnOff() = 0;
virtual void SyncTurnOn() = 0;
virtual void AwaitWebAppQuiescence() = 0;
virtual Profile* GetProfileClient(ProfileClient client) = 0;
};
explicit WebAppIntegrationTestDriver(TestDelegate* delegate);
~WebAppIntegrationTestDriver() override;
// These functions are expected to be called by any test fixtures that use
// this helper.
void SetUp();
void SetUpOnMainThread();
void TearDownOnMainThread();
// Automated Testing Actions
//
// Actions are defined in chrome/test/webapps/data/actions.md
// State change actions:
void HandleAppIdentityUpdateDialogResponse(UpdateDialogResponse response);
void AwaitManifestUpdate(Site site_mode);
void CloseCustomToolbar();
void ClosePwa();
void DisableRunOnOsLogin(Site site);
void EnableRunOnOsLogin(Site site);
void DisableFileHandling(Site site);
void EnableFileHandling(Site site);
void DisableWindowControlsOverlay(Site site);
void EnableWindowControlsOverlay(Site site);
void CreateShortcut(Site site, WindowOptions window_options);
void InstallMenuOption(InstallableSite site);
void InstallLocally(Site site);
void InstallOmniboxIcon(InstallableSite site);
void InstallPolicyApp(Site site,
ShortcutOptions shortcut,
WindowOptions window,
InstallMode mode);
void InstallSubApp(Site parentapp,
Site subapp,
SubAppInstallDialogOptions option);
void RemoveSubApp(Site parentapp, Site subapp);
// These functions install apps which are tabbed and creates shortcuts.
void ApplyRunOnOsLoginPolicyAllowed(Site site);
void ApplyRunOnOsLoginPolicyBlocked(Site site);
void ApplyRunOnOsLoginPolicyRunWindowed(Site site);
void DeletePlatformShortcut(Site site);
void RemoveRunOnOsLoginPolicy(Site site);
void LaunchFileExpectDialog(Site site,
FilesOptions files_options,
AllowDenyOptions allow_deny,
AskAgainOptions ask_again);
void LaunchFileExpectNoDialog(Site site, FilesOptions files_options);
void LaunchFromChromeApps(Site site);
void LaunchFromLaunchIcon(Site site);
void LaunchFromMenuOption(Site site);
void LaunchFromPlatformShortcut(Site site);
#if BUILDFLAG(IS_MAC)
void LaunchFromAppShimFallback(Site site);
#endif
void OpenAppSettingsFromChromeApps(Site site);
void OpenAppSettingsFromAppMenu(Site site);
void CreateShortcutsFromList(Site site);
void NavigateBrowser(Site site);
void NavigatePwa(Site app, Site to);
void NavigateNotfoundUrl();
void ManifestUpdateIcon(Site site, UpdateDialogResponse response);
void ManifestUpdateTitle(Site site,
Title title,
UpdateDialogResponse response);
void ManifestUpdateDisplay(Site site, Display display);
void ManifestUpdateScopeTo(Site app, Site scope);
void OpenInChrome();
void SetOpenInTab(Site site);
void SetOpenInWindow(Site site);
void SwitchIncognitoProfile();
void SwitchProfileClients(ProfileClient client);
void SwitchActiveProfile(ProfileName profile_name);
void SyncTurnOff();
void SyncTurnOn();
void UninstallFromList(Site site);
void UninstallFromMenu(Site site);
void UninstallFromAppSettings(Site site);
void UninstallPolicyApp(Site site);
void UninstallFromOs(Site site);
#if BUILDFLAG(IS_MAC)
void CorruptAppShim(Site site);
#endif
// State Check Actions:
void CheckAppListEmpty();
void CheckAppInListIconCorrect(Site site);
void CheckAppInListNotLocallyInstalled(Site site);
void CheckAppInListWindowed(Site site);
void CheckAppInListTabbed(Site site);
void CheckAppNavigation(Site site);
void CheckAppNavigationIsStartUrl();
void CheckBrowserNavigation(Site site);
void CheckBrowserNavigationIsAppSettings(Site site);
void CheckAppNotInList(Site site);
void CheckAppIcon(Site site, Color color);
void CheckAppTitle(Site site, Title title);
void CheckCreateShortcutNotShown();
void CheckCreateShortcutShown();
void CheckWindowModeIsNotVisibleInAppSettings(Site site);
void CheckFilesLoadedInSite(Site site, FilesOptions files_options);
void CheckInstallIconShown();
void CheckInstallIconNotShown();
void CheckLaunchIconShown();
void CheckLaunchIconNotShown();
void CheckTabCreated();
void CheckTabNotCreated();
void CheckCustomToolbar();
void CheckNoToolbar();
void CheckPlatformShortcutAndIcon(Site site);
void CheckPlatformShortcutNotExists(Site site);
void CheckRunOnOsLoginEnabled(Site site);
void CheckRunOnOsLoginDisabled(Site site);
void CheckSiteHandlesFile(Site site, FileExtension file_extension);
void CheckSiteNotHandlesFile(Site site, FileExtension file_extension);
void CheckUserCannotSetRunOnOsLogin(Site site);
void CheckUserDisplayModeInternal(mojom::UserDisplayMode user_display_mode);
void CheckWindowClosed();
void CheckWindowCreated();
void CheckWindowNotCreated();
void CheckWindowControlsOverlay(Site site, IsOn is_on);
void CheckWindowControlsOverlayToggle(Site site, IsShown is_shown);
void CheckWindowDisplayBrowser();
void CheckWindowDisplayMinimal();
void CheckWindowDisplayStandalone();
void CheckNotHasSubApp(Site subapp);
void CheckHasSubApp(Site subapp);
void CheckNoSubApps();
protected:
// WebAppInstallManagerObserver:
void OnWebAppManifestUpdated(const AppId& app_id,
base::StringPiece old_name) override;
private:
// Must be called at the beginning of every state change action function.
// Returns if the test should continue.
[[nodiscard]] bool BeforeStateChangeAction(const char* function);
// Must be called at the end of every state change action function.
void AfterStateChangeAction();
// Must be called at the beginning of every state check action function.
// Returns if the test should continue.
[[nodiscard]] bool BeforeStateCheckAction(const char* function);
// Must be called at the end of every state check action function.
void AfterStateCheckAction();
void AwaitManifestSystemIdle();
AppId GetAppIdBySiteMode(Site site);
GURL GetUrlForSite(Site site);
absl::optional<AppState> GetAppBySiteMode(StateSnapshot* state_snapshot,
Profile* profile,
Site site);
WebAppProvider* GetProviderForProfile(Profile* profile);
std::unique_ptr<StateSnapshot> ConstructStateSnapshot();
content::WebContents* GetCurrentTab(Browser* browser);
GURL GetInScopeURL(Site site);
base::FilePath GetShortcutPath(base::FilePath shortcut_dir,
const std::string& app_name,
const AppId& app_id);
void InstallPolicyAppInternal(Site site,
base::Value default_launch_container,
const bool create_shortcut,
const bool install_as_shortcut);
void ApplyRunOnOsLoginPolicy(Site site, const char* policy);
void UninstallPolicyAppById(const AppId& id);
void ForceUpdateManifestContents(Site site,
const GURL& app_url_with_manifest_param);
void MaybeNavigateTabbedBrowserInScope(Site site);
enum class NavigationMode { kNewTab, kCurrentTab };
void NavigateTabbedBrowserToSite(const GURL& url, NavigationMode mode);
// Returns an existing app browser if one exists, or launches a new one if
// not.
Browser* GetAppBrowserForSite(Site site, bool launch_if_not_open = true);
bool IsShortcutAndIconCreated(Profile* profile,
const std::string& name,
const AppId& id);
bool DoIconColorsMatch(Profile* profile,
const std::string& name,
const AppId& id);
bool IsFileHandledBySite(Site site, FileExtension file_extension);
void SetFileHandlingEnabled(Site site, bool enabled);
void LaunchFile(Site site, FilesOptions files_options);
void SetRunOnOsLoginMode(Site site, apps::RunOnOsLoginMode login_mode);
void LaunchAppStartupBrowserCreator(const AppId& app_id);
#if BUILDFLAG(IS_MAC)
void LaunchFromAppShim(Site site, const std::vector<GURL>& urls);
#endif
void CheckAppSettingsAppState(Profile* profile, const AppState& app_state);
base::FilePath GetResourceFile(base::FilePath::StringPieceType relative_path);
std::vector<base::FilePath> GetTestFilePaths(FilesOptions file_options);
Browser* browser();
Profile* profile();
std::vector<Profile*> GetAllProfiles();
Browser* app_browser() { return app_browser_; }
WebAppProvider* provider() { return WebAppProvider::GetForTest(profile()); }
PageActionIconView* pwa_install_view();
PageActionIconView* intent_picker_view();
const net::EmbeddedTestServer& GetTestServerForSiteMode(Site site_mode) const;
base::test::ScopedFeatureList scoped_feature_list_;
base::flat_set<AppId> previous_manifest_updates_;
// |waiting_for_update_*| variables are either all populated or all not
// populated. These signify that the test is currently waiting for the
// given |waiting_for_update_id_| to receive an update before continuing.
absl::optional<AppId> waiting_for_update_id_;
std::unique_ptr<base::RunLoop> waiting_for_update_run_loop_;
raw_ptr<TestDelegate> delegate_;
// State snapshots, captured before and after "state change" actions are
// executed, and inspected by "state check" actions to verify behavior.
std::unique_ptr<StateSnapshot> before_state_change_action_state_;
std::unique_ptr<StateSnapshot> after_state_change_action_state_;
// Keeps track if we are currently executing an action. Updated in the
// `BeforeState*Action()` and `AfterState*Action()` methods, and used by the
// manifest update logic to ensure that the action states are appropriately
// kept up to date.
// The number represents the level of nested actions we are in (as an action
// can often call another action).
int executing_action_level_ = 0;
raw_ptr<Profile, DanglingUntriaged> active_profile_ = nullptr;
AppId active_app_id_;
// TODO(crbug.com/1298696): browser_tests breaks with MTECheckedPtr
// enabled. Triage.
raw_ptr<Browser, DanglingUntriagedDegradeToNoOpWhenMTE> app_browser_ =
nullptr;
bool in_tear_down_ = false;
bool is_performing_manifest_update_ = false;
std::unique_ptr<views::NamedWidgetShownWaiter> app_id_update_dialog_waiter_;
base::ScopedObservation<web_app::WebAppInstallManager,
web_app::WebAppInstallManagerObserver>
observation_{this};
std::unique_ptr<OsIntegrationTestOverride::BlockingRegistration>
override_registration_;
std::unique_ptr<base::RunLoop> window_controls_overlay_callback_for_testing_ =
nullptr;
base::flat_set<Site> site_remember_deny_open_file_;
base::AutoReset<absl::optional<web_app::AppIdentityUpdate>>
update_dialog_scope_;
};
// Simple base browsertest class usable by all non-sync web app integration
// tests.
class WebAppIntegrationTest : public InProcessBrowserTest,
public WebAppIntegrationTestDriver::TestDelegate {
public:
WebAppIntegrationTest();
~WebAppIntegrationTest() override;
// InProcessBrowserTest:
void SetUp() override;
// BrowserTestBase:
void SetUpOnMainThread() override;
void TearDownOnMainThread() override;
void SetUpCommandLine(base::CommandLine* command_line) override;
// WebAppIntegrationTestDriver::TestDelegate:
Browser* CreateBrowser(Profile* profile) override;
void CloseBrowserSynchronously(Browser* browser) override;
void AddBlankTabAndShow(Browser* browser) override;
const net::EmbeddedTestServer* EmbeddedTestServer() const override;
Profile* GetDefaultProfile() override;
bool IsSyncTest() override;
void SyncTurnOff() override;
void SyncTurnOn() override;
void AwaitWebAppQuiescence() override;
Profile* GetProfileClient(ProfileClient client) override;
protected:
WebAppIntegrationTestDriver helper_;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
} // namespace web_app::integration_tests
#endif // CHROME_BROWSER_UI_VIEWS_WEB_APPS_WEB_APP_INTEGRATION_TEST_DRIVER_H_