blob: 2bac29f2f1fc83a0977a6bfce3c4713ca24616bc [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/base_paths.h"
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/strings/string_split.h"
#include "base/test/bind.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/banners/test_app_banner_manager_desktop.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/toolbar_button_provider.h"
#include "chrome/browser/ui/views/page_action/page_action_icon_view.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/ui/web_applications/web_app_dialog_utils.h"
#include "chrome/browser/ui/web_applications/web_app_menu_model.h"
#include "chrome/browser/web_applications/components/app_registry_controller.h"
#include "chrome/browser/web_applications/components/install_finalizer.h"
#include "chrome/browser/web_applications/components/os_integration_manager.h"
#include "chrome/browser/web_applications/components/web_app_id.h"
#include "chrome/browser/web_applications/components/web_app_provider_base.h"
#include "chrome/browser/web_applications/test/web_app_install_observer.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_navigation_observer.h"
#include "extensions/browser/extension_dialog_auto_confirm.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/network_switches.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace {
std::string test_case_file = "web_app_integration_browsertest_cases.csv";
std::string platform_name =
#if BUILDFLAG(IS_CHROMEOS_ASH)
"cros";
#elif defined(OS_LINUX)
"linux";
#elif defined(OS_MAC)
"macos";
#elif defined(OS_WIN)
"win";
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
// Returns the path of the requested file in the test data directory.
base::FilePath GetTestFilePath(const std::string& file_name) {
base::FilePath file_path;
base::PathService::Get(base::DIR_SOURCE_ROOT, &file_path);
file_path = file_path.Append(FILE_PATH_LITERAL("chrome"));
file_path = file_path.Append(FILE_PATH_LITERAL("test"));
file_path = file_path.Append(FILE_PATH_LITERAL("data"));
file_path = file_path.Append(FILE_PATH_LITERAL("web_apps"));
return file_path.AppendASCII(file_name);
}
std::vector<std::string> ReadTestInputFile(std::string& file_name) {
base::FilePath file = GetTestFilePath(file_name);
std::string contents;
std::vector<std::string> test_cases;
if (!base::ReadFileToString(file, &contents)) {
LOG(ERROR) << "File not found: " << file.value();
return test_cases;
}
std::vector<std::string> file_lines = base::SplitString(
contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
for (const auto& line : file_lines) {
std::vector<std::string> platforms_and_test = base::SplitString(
line, "|", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
if (platforms_and_test[0] == "all" ||
platforms_and_test[0].find(platform_name) != std::string::npos) {
test_cases.push_back(platforms_and_test[1]);
}
}
return test_cases;
}
} // anonymous namespace
namespace web_app {
struct NavigateToSiteResult {
content::WebContents* web_contents;
webapps::TestAppBannerManagerDesktop* app_banner_manager;
bool installable;
};
class WebAppIntegrationBrowserTest
: public InProcessBrowserTest,
public testing::WithParamInterface<std::string> {
public:
WebAppIntegrationBrowserTest()
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
~WebAppIntegrationBrowserTest() override = default;
WebAppIntegrationBrowserTest(const WebAppIntegrationBrowserTest&) = delete;
WebAppIntegrationBrowserTest& operator=(const WebAppIntegrationBrowserTest&) =
delete;
// InProcessBrowserTest
void SetUp() override {
https_server_.AddDefaultHandlers(GetChromeTestDataDir());
ASSERT_TRUE(https_server_.Start());
webapps::TestAppBannerManagerDesktop::SetUp();
InProcessBrowserTest::SetUp();
}
// BrowserTestBase
void SetUpOnMainThread() override {
os_hooks_suppress_ =
OsIntegrationManager::ScopedSuppressOsHooksForTesting();
pwa_install_view_ =
BrowserView::GetBrowserViewForBrowser(browser())
->toolbar_button_provider()
->GetPageActionIconView(PageActionIconType::kPwaInstall);
ASSERT_TRUE(pwa_install_view_);
EXPECT_FALSE(pwa_install_view_->GetVisible());
}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(
network::switches::kUnsafelyTreatInsecureOriginAsSecure,
GetInstallableAppURL().GetOrigin().spec());
}
// Test Framework
void ParseParams() {
std::string action_strings = GetParam();
testing_actions_ = base::SplitString(
action_strings, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
}
void ExecuteAction(const std::string& action_string) {
if (base::StartsWith(action_string, "navigate_installable")) {
NavigateToSite(browser(), GetInstallableAppURL());
} else if (action_string == "navigate_browser_in_scope") {
NavigateToSite(browser(), GetInScopeURL());
} else if (action_string == "navigate_not_installable") {
NavigateToSite(browser(), GetOutOfScopeURL());
} else if (action_string == "install_omnibox_or_menu") {
ExecutePwaInstallIcon();
} else if (base::StartsWith(action_string, "launch_internal")) {
LaunchInternal();
} else if (action_string == "uninstall_from_menu") {
UninstallFromMenu();
} else if (action_string == "uninstall_internal") {
UninstallInternal();
} else if (action_string == "install_create_shortcut_tabbed") {
InstallCreateShortcutTabbed();
} else if (action_string == "set_open_in_window_internal") {
SetOpenInWindowInternal();
} else if (action_string == "close_pwa") {
ClosePWA();
} else if (action_string == "assert_installable") {
AssertInstallable();
} else if (action_string == "assert_install_icon_shown") {
AssertInstallIconShown();
} else if (action_string == "assert_install_icon_not_shown") {
AssertInstallIconNotShown();
} else if (action_string == "assert_launch_icon_shown") {
AssertLaunchIconShown();
} else if (action_string == "assert_launch_icon_not_shown") {
AssertLaunchIconNotShown();
} else if (action_string == "assert_window_created") {
AssertWindowCreated();
} else if (action_string == "assert_no_crash") {
} else {
FAIL() << "Unimplemented action: " << action_string;
}
}
// Automated Testing Actions
NavigateToSiteResult NavigateToSite(Browser* browser, const GURL& url) {
content::WebContents* web_contents = GetCurrentTab(browser);
auto* app_banner_manager =
webapps::TestAppBannerManagerDesktop::FromWebContents(web_contents);
DCHECK(!app_banner_manager->WaitForInstallableCheck());
ui_test_utils::NavigateToURL(browser, url);
bool installable = app_banner_manager->WaitForInstallableCheck();
last_navigation_result_ =
NavigateToSiteResult{web_contents, app_banner_manager, installable};
return last_navigation_result_;
}
GURL GetInstallableAppURL() {
return https_server_.GetURL("/banners/manifest_test_page.html");
}
GURL GetInScopeURL() {
return https_server_.GetURL("/banners/manifest_test_page.html");
}
GURL GetOutOfScopeURL() {
return https_server_.GetURL("/out_of_scope/index.html");
}
content::WebContents* GetCurrentTab(Browser* browser) {
return browser->tab_strip_model()->GetActiveWebContents();
}
web_app::AppId ExecutePwaInstallIcon() {
chrome::SetAutoAcceptPWAInstallConfirmationForTesting(true);
web_app::AppId app_id;
base::RunLoop run_loop;
web_app::SetInstalledCallbackForTesting(base::BindLambdaForTesting(
[&app_id, &run_loop](const web_app::AppId& installed_app_id,
web_app::InstallResultCode code) {
app_id = installed_app_id;
run_loop.Quit();
}));
pwa_install_view()->ExecuteForTesting();
run_loop.Run();
chrome::SetAutoAcceptPWAInstallConfirmationForTesting(false);
app_id_ = app_id;
auto* browser_list = BrowserList::GetInstance();
app_browser_ = browser_list->GetLastActive();
DCHECK(AppBrowserController::IsWebApp(app_browser_));
return app_id;
}
Browser* LaunchInternal() {
app_browser_ = LaunchWebAppBrowserAndWait(
ProfileManager::GetActiveUserProfile(), app_id_);
return app_browser_;
}
// TODO(https://crbug.com/1159651): Support this action on CrOS.
void UninstallFromMenu() {
DCHECK(app_browser_);
base::RunLoop run_loop;
WebAppInstallObserver observer(browser()->profile());
observer.SetWebAppUninstalledDelegate(
base::BindLambdaForTesting([&](const AppId& app_id) {
if (app_id == app_id_) {
run_loop.Quit();
}
}));
extensions::ScopedTestDialogAutoConfirm auto_confirm(
extensions::ScopedTestDialogAutoConfirm::ACCEPT);
auto app_menu_model =
std::make_unique<WebAppMenuModel>(nullptr, app_browser_);
app_menu_model->Init();
ui::MenuModel* model = app_menu_model.get();
int index = -1;
const bool found = app_menu_model->GetModelAndIndexForCommandId(
WebAppMenuModel::kUninstallAppCommandId, &model, &index);
EXPECT_TRUE(found);
EXPECT_TRUE(model->IsEnabledAt(index));
app_menu_model->ExecuteCommand(WebAppMenuModel::kUninstallAppCommandId,
/*event_flags=*/0);
// The |app_menu_model| must be destroyed here, as the |run_loop| waits
// until the app is fully uninstalled, which includes closing and deleting
// the app_browser_.
app_menu_model.reset();
app_browser_ = nullptr;
run_loop.Run();
}
void UninstallInternal() {
WebAppProviderBase* const provider =
WebAppProviderBase::GetProviderBase(browser()->profile());
base::RunLoop run_loop;
DCHECK(provider->install_finalizer().CanUserUninstallExternalApp(app_id_));
provider->install_finalizer().UninstallExternalAppByUser(
app_id_, base::BindLambdaForTesting([&](bool uninstalled) {
EXPECT_TRUE(uninstalled);
run_loop.Quit();
}));
run_loop.Run();
}
void InstallCreateShortcutTabbed() {
chrome::SetAutoAcceptWebAppDialogForTesting(/*auto_accept=*/true,
/*auto_open_in_window=*/false);
WebAppInstallObserver observer(browser()->profile());
CHECK(chrome::ExecuteCommand(browser(), IDC_CREATE_SHORTCUT));
app_id_ = observer.AwaitNextInstall();
chrome::SetAutoAcceptWebAppDialogForTesting(false, false);
}
void SetOpenInWindowInternal() {
auto& app_registry_controller =
WebAppProvider::Get(browser()->profile())->registry_controller();
app_registry_controller.SetAppUserDisplayMode(
app_id_, blink::mojom::DisplayMode::kStandalone, true);
}
void ClosePWA() {
DCHECK(app_browser_);
app_browser_->window()->Close();
ui_test_utils::WaitForBrowserToClose(app_browser_);
}
// Assert Actions
void AssertInstallable() { EXPECT_TRUE(last_navigation_result_.installable); }
void AssertInstallIconShown() {
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, browser()), kEnabled);
EXPECT_TRUE(pwa_install_view()->GetVisible());
}
void AssertInstallIconNotShown() {
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, browser()), kNotPresent);
EXPECT_FALSE(pwa_install_view()->GetVisible());
}
void AssertLaunchIconShown() {
EXPECT_EQ(GetAppMenuCommandState(IDC_OPEN_IN_PWA_WINDOW, browser()),
kEnabled);
}
void AssertLaunchIconNotShown() {
EXPECT_EQ(GetAppMenuCommandState(IDC_OPEN_IN_PWA_WINDOW, browser()),
kNotPresent);
}
void AssertWindowCreated() { EXPECT_TRUE(app_browser_); }
Browser* app_browser() { return app_browser_; }
std::vector<std::string>& testing_actions() { return testing_actions_; }
PageActionIconView* pwa_install_view() { return pwa_install_view_; }
private:
Browser* app_browser_ = nullptr;
std::vector<std::string> testing_actions_;
NavigateToSiteResult last_navigation_result_;
AppId app_id_;
net::EmbeddedTestServer https_server_;
PageActionIconView* pwa_install_view_ = nullptr;
ScopedOsHooksSuppress os_hooks_suppress_;
};
// Tests that installing a PWA will cause the install icon to be hidden, and
// the launch icon to be shown.
IN_PROC_BROWSER_TEST_F(WebAppIntegrationBrowserTest,
InstallAndVerifyUIUpdates) {
bool installable =
NavigateToSite(browser(), GetInstallableAppURL()).installable;
ASSERT_TRUE(installable);
EXPECT_EQ(GetAppMenuCommandState(IDC_CREATE_SHORTCUT, browser()), kEnabled);
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, browser()), kEnabled);
EXPECT_TRUE(pwa_install_view()->GetVisible());
EXPECT_EQ(GetAppMenuCommandState(IDC_OPEN_IN_PWA_WINDOW, browser()),
kNotPresent);
ExecutePwaInstallIcon();
chrome::NewTab(browser());
NavigateToSite(browser(), GetInstallableAppURL());
EXPECT_EQ(GetAppMenuCommandState(IDC_INSTALL_PWA, browser()), kNotPresent);
EXPECT_FALSE(pwa_install_view()->GetVisible());
EXPECT_EQ(GetAppMenuCommandState(IDC_OPEN_IN_PWA_WINDOW, browser()),
kEnabled);
}
IN_PROC_BROWSER_TEST_F(WebAppIntegrationBrowserTest, LaunchInternal) {
auto* browser_list = BrowserList::GetInstance();
EXPECT_EQ(1U, browser_list->size());
EXPECT_FALSE(AppBrowserController::IsWebApp(browser_list->GetLastActive()));
NavigateToSite(browser(), GetInstallableAppURL());
ExecutePwaInstallIcon();
EXPECT_EQ(2U, browser_list->size());
EXPECT_TRUE(AppBrowserController::IsWebApp(browser_list->GetLastActive()));
ClosePWA();
EXPECT_EQ(1U, browser_list->size());
EXPECT_FALSE(AppBrowserController::IsWebApp(browser_list->GetLastActive()));
LaunchInternal();
EXPECT_EQ(2U, browser_list->size());
EXPECT_TRUE(AppBrowserController::IsWebApp(browser_list->GetLastActive()));
}
IN_PROC_BROWSER_TEST_P(WebAppIntegrationBrowserTest, Default) {
ParseParams();
for (auto& action : testing_actions()) {
ExecuteAction(action);
}
}
INSTANTIATE_TEST_SUITE_P(All,
WebAppIntegrationBrowserTest,
testing::ValuesIn(ReadTestInputFile(test_case_file)));
} // namespace web_app