chromium/chrome/browser/ash/system_web_apps/apps/help_app/help_app_integration_browsertest.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <optional>
#include <string>
#include <vector>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/webui/help_app_ui/buildflags.h"
#include "ash/webui/help_app_ui/help_app_manager.h"
#include "ash/webui/help_app_ui/help_app_manager_factory.h"
#include "ash/webui/help_app_ui/search/search.mojom.h"
#include "ash/webui/help_app_ui/search/search_handler.h"
#include "ash/webui/help_app_ui/url_constants.h"
#include "ash/webui/web_applications/test/sandboxed_web_ui_test_base.h"
#include "base/command_line.h"
#include "base/containers/to_vector.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/apps/app_service/app_install/app_install_service_ash.h"
#include "chrome/browser/apps/app_service/app_launch_params.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/launch_utils.h"
#include "chrome/browser/apps/app_service/metrics/app_service_metrics.h"
#include "chrome/browser/ash/first_run/first_run.h"
#include "chrome/browser/ash/release_notes/release_notes_notification.h"
#include "chrome/browser/ash/release_notes/release_notes_storage.h"
#include "chrome/browser/ash/system_web_apps/apps/help_app/help_app_notification_controller.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/ash/system_web_apps/test_support/system_web_app_browsertest_base.h"
#include "chrome/browser/ash/system_web_apps/test_support/system_web_app_integration_test.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/ui/ash/system/system_tray_client_impl.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/webui/ash/system_web_dialog_delegate.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/language/core/browser/pref_names.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/services/app_service/public/cpp/package_id.h"
#include "components/supervised_user/core/common/pref_names.h"
#include "components/supervised_user/core/common/supervised_user_constants.h"
#include "components/user_manager/user_names.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/no_renderer_crashes_assertion.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/idle/idle.h"
#include "ui/base/idle/scoped_set_idle_state.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/display/screen.h"
#include "ui/display/types/display_constants.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/widget/any_widget_observer.h"
#include "ui/views/widget/widget.h"
#include "url/url_constants.h"

namespace ash {

namespace {
constexpr char kExploreUpdatesPageUrl[] =
    "chrome://help-app/updates?launchSource=version-update";

class HelpAppIntegrationTest : public SystemWebAppIntegrationTest {
 public:
  HelpAppIntegrationTest()
      : https_server_{std::make_unique<net::EmbeddedTestServer>(
            net::EmbeddedTestServer::TYPE_HTTPS)} {
    scoped_feature_list_.InitWithFeatures(
        {chromeos::features::kUploadOfficeToCloud,
         features::kReleaseNotesNotificationAllChannels,
         features::kHelpAppLauncherSearch},
        {features::kHelpAppOpensInsteadOfReleaseNotesNotification});
    https_server()->AddDefaultHandlers(GetChromeTestDataDir());
  }

  // Setting up our own HTTPS `EmbeddedTestServer` because the superclass's
  // `embedded_test_server()` is HTTP.
  net::EmbeddedTestServer* https_server() { return https_server_.get(); }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<net::EmbeddedTestServer> https_server_;
};

using HelpAppAllProfilesIntegrationTest = HelpAppIntegrationTest;

content::WebContents* GetActiveWebContents() {
  return chrome::FindLastActive()->tab_strip_model()->GetActiveWebContents();
}

class HelpAppIntegrationTestWithAutoTriggerDisabled
    : public HelpAppIntegrationTest {
 public:
  HelpAppIntegrationTestWithAutoTriggerDisabled() {
    scoped_feature_list_.InitAndDisableFeature(
        features::kHelpAppAutoTriggerInstallDialog);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

class HelpAppIntegrationTestWithFirstRunEnabled
    : public HelpAppIntegrationTest {
 public:
  HelpAppIntegrationTestWithFirstRunEnabled() = default;

 private:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    HelpAppIntegrationTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch(ash::switches::kForceFirstRunUI);
  }
};

class HelpAppIntegrationTestWithHelpAppOpensInsteadOfReleaseNotesNotification
    : public HelpAppIntegrationTest {
 private:
  base::test::ScopedFeatureList scoped_feature_list_{
      features::kHelpAppOpensInsteadOfReleaseNotesNotification};
};

class HelpAppIntegrationTestWithBirchFeatureEnabled
    : public HelpAppIntegrationTest {
 public:
  HelpAppIntegrationTestWithBirchFeatureEnabled() {
    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/
        {features::kHelpAppOpensInsteadOfReleaseNotesNotification,
         features::kForestFeature},
        /*disabled_features=*/{});
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

}  // namespace

// Test that the Help App installs and launches correctly. Runs some spot
// checks on the manifest.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2) {
  const GURL url(kChromeUIHelpAppURL);
  EXPECT_NO_FATAL_FAILURE(
      ExpectSystemWebAppValid(SystemWebAppType::HELP, url, "Explore"));
}

// Test that the Help App is searchable by additional strings.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2SearchInLauncher) {
  WaitForTestSystemAppInstall();
  auto* system_app = GetManager().GetSystemApp(SystemWebAppType::HELP);
  EXPECT_THAT(base::ToVector(system_app->GetAdditionalSearchTerms(),
                             &l10n_util::GetStringUTF8),
              testing::ElementsAre("Get Help", "Perks", "Offers"));
}

// Test that the Help App has a minimum window size of 600x320.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2MinWindowSize) {
  WaitForTestSystemAppInstall();
  auto* system_app = GetManager().GetSystemApp(SystemWebAppType::HELP);
  EXPECT_EQ(system_app->GetMinimumWindowSize(), gfx::Size(600, 320));
}

// Test that the Help App has a default size of 960x600 and is in the center of
// the screen.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2DefaultWindowBounds) {
  WaitForTestSystemAppInstall();
  Browser* browser;
  LaunchApp(SystemWebAppType::HELP, &browser);
  gfx::Rect work_area =
      display::Screen::GetScreen()->GetDisplayForNewWindows().work_area();
  int x = (work_area.width() - 960) / 2;
  int y = (work_area.height() - 600) / 2;
  EXPECT_EQ(browser->window()->GetBounds(), gfx::Rect(x, y, 960, 600));
}

// Test that the Help App logs metric when launching the app using the
// AppServiceProxy.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2AppServiceMetrics) {
  WaitForTestSystemAppInstall();
  base::HistogramTester histogram_tester;

  // Using AppServiceProxy gives more coverage of the launch path and ensures
  // the metric is not recorded twice.
  auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile());
  content::TestNavigationObserver navigation_observer(
      GURL("chrome://help-app/"));
  navigation_observer.StartWatchingNewWebContents();

  proxy->Launch(*GetManager().GetAppIdForSystemApp(SystemWebAppType::HELP),
                ui::EF_NONE, apps::LaunchSource::kFromKeyboard,
                std::make_unique<apps::WindowInfo>(display::kDefaultDisplayId));

  navigation_observer.Wait();
  histogram_tester.ExpectUniqueSample("Apps.DefaultAppLaunch.FromKeyboard",
                                      apps::DefaultAppName::kHelpApp, 1);
  histogram_tester.ExpectUniqueSample("Discover.Overall.AppLaunched",
                                      apps::LaunchSource::kFromKeyboard, 1);
}

// Test that the Help App can log metrics in the untrusted frame.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2InAppMetrics) {
  WaitForTestSystemAppInstall();
  content::WebContents* web_contents = LaunchApp(SystemWebAppType::HELP);

  base::UserActionTester user_action_tester;

  constexpr char kScript[] = R"(
    chrome.metricsPrivate.recordUserAction("Discover.Help.TabClicked");
  )";

  EXPECT_EQ(0, user_action_tester.GetActionCount("Discover.Help.TabClicked"));
  EXPECT_EQ(nullptr,
            SandboxedWebUiAppTestBase::EvalJsInAppFrame(web_contents, kScript));
  EXPECT_EQ(1, user_action_tester.GetActionCount("Discover.Help.TabClicked"));
}

IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest,
                       HasCorrectThemeAndBackgroundColor) {
  WaitForTestSystemAppInstall();
  webapps::AppId app_id =
      *GetManager().GetAppIdForSystemApp(SystemWebAppType::HELP);
  web_app::WebAppRegistrar& registrar =
      web_app::WebAppProvider::GetForTest(profile())->registrar_unsafe();

  EXPECT_EQ(registrar.GetAppThemeColor(app_id), SK_ColorWHITE);
  EXPECT_EQ(registrar.GetAppBackgroundColor(app_id), SK_ColorWHITE);
  EXPECT_EQ(registrar.GetAppDarkModeThemeColor(app_id), gfx::kGoogleGrey900);
  EXPECT_EQ(registrar.GetAppDarkModeBackgroundColor(app_id),
            gfx::kGoogleGrey900);
}

IN_PROC_BROWSER_TEST_P(HelpAppAllProfilesIntegrationTest, HelpAppV2ShowHelp) {
  // TODO(b/287166176): Fix the test and remove this.
#if !BUILDFLAG(GOOGLE_CHROME_BRANDING)
  if (GetParam().crosapi_state == TestProfileParam::CrosapiParam::kEnabled) {
    GTEST_SKIP()
        << "Skipping test body for CrosapiParam::kEnabled, see b/287166176.";
  }
#endif

  WaitForTestSystemAppInstall();

  GURL expected_url = GURL("chrome://help-app/");
  content::TestNavigationObserver navigation_observer(expected_url);
  navigation_observer.StartWatchingNewWebContents();

  chrome::ShowHelp(browser(), chrome::HELP_SOURCE_KEYBOARD);

#if BUILDFLAG(ENABLE_CROS_HELP_APP)
  EXPECT_NO_FATAL_FAILURE(navigation_observer.Wait());
  // Help app should have opened at the expected page.
  EXPECT_EQ(expected_url, GetActiveWebContents()->GetVisibleURL());
#else
  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
  EXPECT_EQ(GURL(chrome::kChromeHelpViaKeyboardURL),
            GetActiveWebContents()->GetVisibleURL());
#endif
}

// Test that first run experience opens Help App with launch source query param.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTestWithFirstRunEnabled,
                       HelpAppV2FirstRunLaunch) {
  WaitForTestSystemAppInstall();
  base::HistogramTester histogram_tester;
  GURL expected_trusted_frame_url =
      GURL("chrome://help-app?launchSource=first-run");
  content::TestNavigationObserver navigation_observer(
      expected_trusted_frame_url);
  navigation_observer.StartWatchingNewWebContents();

  // Then call the launch method used by the first run experience.
  ash::first_run::LaunchHelpApp(profile());

#if BUILDFLAG(ENABLE_CROS_HELP_APP)
  EXPECT_NO_FATAL_FAILURE(navigation_observer.Wait());
  // The Help app trusted frame should have opened at the expected page.
  EXPECT_EQ(expected_trusted_frame_url,
            GetActiveWebContents()->GetVisibleURL());

  // The Help app untrusted frame should contain the same query param.
  EXPECT_EQ("chrome-untrusted://help-app/?launchSource=first-run",
            SandboxedWebUiAppTestBase::EvalJsInAppFrame(
                GetActiveWebContents(), "window.location.href"));

  histogram_tester.ExpectUniqueSample("Apps.DefaultAppLaunch.FromFirstRun",
                                      apps::DefaultAppName::kHelpApp, 1);
  histogram_tester.ExpectUniqueSample("Discover.Overall.AppLaunched",
                                      apps::LaunchSource::kFromFirstRun, 1);
#else
  // We just have the original browser. No new app opens.
  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
  histogram_tester.ExpectUniqueSample("Apps.DefaultAppLaunch.FromFirstRun",
                                      apps::DefaultAppName::kHelpApp, 0);
  histogram_tester.ExpectUniqueSample("Discover.Overall.AppLaunched",
                                      apps::LaunchSource::kFromFirstRun, 0);
#endif
}

// Test that launching the Help App's release notes opens the app on the Release
// Notes page.
IN_PROC_BROWSER_TEST_P(HelpAppAllProfilesIntegrationTest,
                       HelpAppV2LaunchReleaseNotes) {
  WaitForTestSystemAppInstall();

  // There should be 1 browser window initially.
  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());

  const GURL expected_url("chrome://help-app/updates");
  content::TestNavigationObserver navigation_observer(expected_url);
  navigation_observer.StartWatchingNewWebContents();

  chrome::LaunchReleaseNotes(profile(), apps::LaunchSource::kFromOtherApp);
#if BUILDFLAG(ENABLE_CROS_HELP_APP)
  // If no navigation happens, then this test will time out due to the wait.
  navigation_observer.Wait();

  // There should be two browser windows, one regular and one for the newly
  // opened app.
  EXPECT_EQ(2u, chrome::GetTotalBrowserCount());

  // The opened window should be showing the url with attached WebUI.
  // The inner frame should be the pathname for the release notes pathname.
  EXPECT_EQ("chrome-untrusted://help-app/updates",
            SandboxedWebUiAppTestBase::EvalJsInAppFrame(
                GetActiveWebContents(), "window.location.href"));
#else
  // Nothing should happen on non-branded builds.
  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
#endif
}

// Test that launching the Help App's release notes logs metrics.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2ReleaseNotesMetrics) {
  WaitForTestSystemAppInstall();

  const GURL expected_url("chrome://help-app/updates");
  content::TestNavigationObserver navigation_observer(expected_url);
  navigation_observer.StartWatchingNewWebContents();

  base::UserActionTester user_action_tester;
  chrome::LaunchReleaseNotes(profile(), apps::LaunchSource::kFromOtherApp);
#if BUILDFLAG(ENABLE_CROS_HELP_APP)
  // If no navigation happens, then this test will time out due to the wait.
  navigation_observer.Wait();

  EXPECT_EQ(1,
            user_action_tester.GetActionCount("ReleaseNotes.ShowReleaseNotes"));
#else
  EXPECT_EQ(0,
            user_action_tester.GetActionCount("ReleaseNotes.ShowReleaseNotes"));
#endif
}

// Test that clicking the release notes notification opens Help App.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest,
                       HelpAppV2LaunchReleaseNotesFromNotification) {
  // TODO(http://b/349164737): Re-enable this test with forest feature enabled.
  if (ash::features::IsForestFeatureEnabled()) {
    GTEST_SKIP() << "Skipping test body for Forest Feature.";
  }

  WaitForTestSystemAppInstall();
  base::UserActionTester user_action_tester;
  auto display_service =
      std::make_unique<NotificationDisplayServiceTester>(/*profile=*/nullptr);
  auto release_notes_notification =
      std::make_unique<ReleaseNotesNotification>(profile());
  auto release_notes_storage = std::make_unique<ReleaseNotesStorage>(profile());

  // Force the release notes notification to show up.
  profile()->GetPrefs()->SetInteger(
      prefs::kHelpAppNotificationLastShownMilestone, 20);
  release_notes_notification->MaybeShowReleaseNotes();
  // Assert that the notification really is there.
  auto notifications = display_service->GetDisplayedNotificationsForType(
      NotificationHandler::Type::TRANSIENT);
  ASSERT_EQ(1u, notifications.size());
  ASSERT_EQ("show_release_notes_notification", notifications[0].id());

  GURL expected_url = GURL("chrome://help-app/updates");
  content::TestNavigationObserver navigation_observer(expected_url);
  navigation_observer.StartWatchingNewWebContents();
  // Then click.
  display_service->SimulateClick(NotificationHandler::Type::TRANSIENT,
                                 "show_release_notes_notification",
                                 std::nullopt, std::nullopt);

  EXPECT_EQ(
      1, user_action_tester.GetActionCount("ReleaseNotes.NotificationShown"));
  EXPECT_EQ(1, user_action_tester.GetActionCount(
                   "ReleaseNotes.LaunchedNotification"));
#if BUILDFLAG(ENABLE_CROS_HELP_APP)
  EXPECT_NO_FATAL_FAILURE(navigation_observer.Wait());
  // Help app should have opened at the expected page.
  EXPECT_EQ(expected_url, GetActiveWebContents()->GetVisibleURL());
  EXPECT_EQ(1,
            user_action_tester.GetActionCount("ReleaseNotes.ShowReleaseNotes"));
#else
  // We just have the original browser. No new app opens.
  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
  EXPECT_EQ(0,
            user_action_tester.GetActionCount("ReleaseNotes.ShowReleaseNotes"));
#endif
}

// Test that the background page can trigger the release notes notification.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest,
                       HelpAppV2ReleaseNotesNotificationFromBackground) {
  WaitForTestSystemAppInstall();
  content::WebContents* web_contents = LaunchApp(SystemWebAppType::HELP);
  auto display_service =
      std::make_unique<NotificationDisplayServiceTester>(/*profile=*/nullptr);
  base::UserActionTester user_action_tester;
  profile()->GetPrefs()->SetInteger(
      prefs::kHelpAppNotificationLastShownMilestone, 20);
  EXPECT_EQ(profile()->GetPrefs()->GetInteger(
                prefs::kReleaseNotesSuggestionChipTimesLeftToShow),
            0);

  // Script that simulates what the Help App background page would do to show
  // the release notes notification.
  constexpr char kScript[] = R"(
    (async () => {
      const delegate = window.customLaunchData.delegate;
      await delegate.maybeShowReleaseNotesNotification();
      return true;
    })();
  )";
  // Use EvalJs instead of EvalJsInAppFrame because the script needs to
  // run in the same world as the page's code.
  EXPECT_EQ(true,
            content::EvalJs(
                SandboxedWebUiAppTestBase::GetAppFrame(web_contents), kScript));
  if (features::IsForestFeatureEnabled()) {
    EXPECT_EQ(profile()->GetPrefs()->GetInteger(
                  prefs::kReleaseNotesSuggestionChipTimesLeftToShow),
              0);
  } else {
    EXPECT_EQ(profile()->GetPrefs()->GetInteger(
                  prefs::kReleaseNotesSuggestionChipTimesLeftToShow),
              3);
  }

  Browser* browser = chrome::FindBrowserWithTab(web_contents);
  // Close the web contents we just created to simulate what would happen in
  // production with a background page. This helps us ensure that our
  // notification shows up and can be interacted with even after the web ui
  // that triggered it has died.
  web_contents->Close();
  // Wait until the browser with the web contents closes.
  ui_test_utils::WaitForBrowserToClose(browser);
  // Assert that the notification really is there.
  auto notifications = display_service->GetDisplayedNotificationsForType(
      NotificationHandler::Type::TRANSIENT);
  if (features::IsForestFeatureEnabled()) {
    ASSERT_EQ(0u, notifications.size());
  } else {
    ASSERT_EQ(1u, notifications.size());
    ASSERT_EQ("show_release_notes_notification", notifications[0].id());
  }
  // Click on the notification.
  GURL expected_url = GURL("chrome://help-app/updates");
  content::TestNavigationObserver navigation_observer(expected_url);
  navigation_observer.StartWatchingNewWebContents();
  display_service->SimulateClick(NotificationHandler::Type::TRANSIENT,
                                 "show_release_notes_notification",
                                 std::nullopt, std::nullopt);
#if BUILDFLAG(ENABLE_CROS_HELP_APP)
  if (!features::IsForestFeatureEnabled()) {
    EXPECT_NO_FATAL_FAILURE(navigation_observer.Wait());
    EXPECT_EQ(expected_url, GetActiveWebContents()->GetVisibleURL());
  }
#else
  // We just have the original browser. No new app opens.
  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
#endif
}

IN_PROC_BROWSER_TEST_P(
    HelpAppIntegrationTestWithHelpAppOpensInsteadOfReleaseNotesNotification,
    OpensHelpApp) {
  WaitForTestSystemAppInstall();
  base::HistogramTester histogram_tester;
  GURL expected_trusted_frame_url = GURL(kExploreUpdatesPageUrl);
  content::TestNavigationObserver navigation_observer(
      expected_trusted_frame_url);
  navigation_observer.StartWatchingNewWebContents();
  auto display_service =
      std::make_unique<NotificationDisplayServiceTester>(/*profile=*/nullptr);

  profile()->GetPrefs()->SetInteger(
      prefs::kHelpAppNotificationLastShownMilestone, 20);
  std::make_unique<HelpAppNotificationController>(profile())
      ->MaybeShowReleaseNotesNotification();

  // The release notes notification should not appear.
  auto notifications = display_service->GetDisplayedNotificationsForType(
      NotificationHandler::Type::TRANSIENT);
  EXPECT_EQ(0u, notifications.size());
  // The release notes suggestion chip should not appear.
  EXPECT_EQ(profile()->GetPrefs()->GetInteger(
                prefs::kReleaseNotesSuggestionChipTimesLeftToShow),
            0);
#if BUILDFLAG(ENABLE_CROS_HELP_APP)
  if (!features::IsForestFeatureEnabled()) {
    EXPECT_NO_FATAL_FAILURE(navigation_observer.Wait());
    EXPECT_EQ(expected_trusted_frame_url,
              GetActiveWebContents()->GetVisibleURL());
    histogram_tester.ExpectUniqueSample("Discover.Overall.AppLaunched",
                                        apps::LaunchSource::kFromOsLogin, 1);
  }
#else
  // We just have the original browser. No new app opens.
  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
  histogram_tester.ExpectUniqueSample("Discover.Overall.AppLaunched",
                                      apps::LaunchSource::kFromOsLogin, 0);
#endif
}

IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTestWithBirchFeatureEnabled,
                       HelpAppRemainsClosed) {
  WaitForTestSystemAppInstall();
  base::HistogramTester histogram_tester;
  GURL expected_trusted_frame_url = GURL(kExploreUpdatesPageUrl);
  content::TestNavigationObserver navigation_observer(
      expected_trusted_frame_url);
  navigation_observer.StartWatchingNewWebContents();
  auto display_service =
      std::make_unique<NotificationDisplayServiceTester>(/*profile=*/nullptr);

  profile()->GetPrefs()->SetInteger(
      prefs::kHelpAppNotificationLastShownMilestone, 20);
  std::make_unique<HelpAppNotificationController>(profile())
      ->MaybeShowReleaseNotesNotification();

  // The release notes notification should not appear.
  auto notifications = display_service->GetDisplayedNotificationsForType(
      NotificationHandler::Type::TRANSIENT);
  EXPECT_EQ(0u, notifications.size());
  // The release notes suggestion chip should not appear.
  EXPECT_EQ(profile()->GetPrefs()->GetInteger(
                prefs::kReleaseNotesSuggestionChipTimesLeftToShow),
            0);
#if BUILDFLAG(ENABLE_CROS_HELP_APP)
  // No new app should open because of birch flag.
  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
  histogram_tester.ExpectUniqueSample(
      "Discover.Overall.AppLaunched",
      apps::LaunchSource::kFromReleaseNotesNotification, 0);
#else
  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
  histogram_tester.ExpectUniqueSample(
      "Discover.Overall.AppLaunched",
      apps::LaunchSource::kFromReleaseNotesNotification, 0);
#endif
}

// Test that the Help App does a navigation on launch even when it was already
// open with the same URL.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2NavigateOnRelaunch) {
  WaitForTestSystemAppInstall();

  // There should initially be a single browser window.
  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());

  Browser* browser;
  content::WebContents* web_contents =
      LaunchApp(SystemWebAppType::HELP, &browser);

  // There should be two browser windows, one regular and one for the newly
  // opened app.
  EXPECT_EQ(2u, chrome::GetTotalBrowserCount());

  content::TestNavigationObserver navigation_observer(web_contents);
  LaunchAppWithoutWaiting(SystemWebAppType::HELP);
  // If no navigation happens, then this test will time out due to the wait.
  navigation_observer.Wait();

  // LaunchApp should navigate the existing window and not open any new windows.
  EXPECT_EQ(browser, chrome::FindLastActive());
  EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
}

// Test direct navigation to a subpage.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2DirectNavigation) {
  WaitForTestSystemAppInstall();
  auto params = LaunchParamsForApp(SystemWebAppType::HELP);
  params.override_url = GURL("chrome://help-app/help/");

  content::WebContents* web_contents = LaunchApp(std::move(params));

  // The inner frame should have the same pathname as the launch URL.
  EXPECT_EQ("chrome-untrusted://help-app/help/",
            SandboxedWebUiAppTestBase::EvalJsInAppFrame(
                web_contents, "window.location.href"));
}

// Test that the Help App can open the feedback dialog.
//
// Flaky on Linux Chromium OS ASan LSan Tests (1)
//
// TODO(crbug.com/40940376): Reenable it.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest,
                       DISABLED_HelpAppV2OpenFeedbackDialog) {
  WaitForTestSystemAppInstall();
  content::WebContents* web_contents = LaunchApp(SystemWebAppType::HELP);

  // Script that tells the Help App to open the feedback dialog.
  constexpr char kScript[] = R"(
    (async () => {
      const res = await window.customLaunchData.delegate.openFeedbackDialog();
      return res === null;
    })();
  )";
  // A null string result means no error in opening feedback.
  EXPECT_EQ(true,
            content::EvalJs(
                SandboxedWebUiAppTestBase::GetAppFrame(web_contents), kScript));
}

// Test that the Help App can open the on device app controls part section in OS
// Settings.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest,
                       HelpAppV2ShowOnDeviceAppControls) {
  WaitForTestSystemAppInstall();
  content::WebContents* web_contents = LaunchApp(SystemWebAppType::HELP);

  // There should be two browser windows, one regular and one for the newly
  // opened help app.
  EXPECT_EQ(2u, chrome::GetTotalBrowserCount());

  const GURL expected_url("chrome://os-settings/apps");
  content::TestNavigationObserver navigation_observer(expected_url);
  navigation_observer.StartWatchingNewWebContents();

  // Script that tells the Help App to show on device app controls.
  constexpr char kScript[] = R"(
    (async () => {
      await window.customLaunchData.delegate.showOnDeviceAppControls();
    })();
  )";
  // Trigger the script, then wait for settings to open. Use ExecJs
  // instead of EvalJsInAppFrame because the script needs to run in the same
  // world as the page's code.
  EXPECT_TRUE(content::ExecJs(
      SandboxedWebUiAppTestBase::GetAppFrame(web_contents), kScript));
  navigation_observer.Wait();

  // Settings should be active in a new window.
  EXPECT_EQ(3u, chrome::GetTotalBrowserCount());
  EXPECT_EQ(expected_url, GetActiveWebContents()->GetVisibleURL());
}

// Test that the Help App opens the OS Settings family link page.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2ShowParentalControls) {
  WaitForTestSystemAppInstall();
  content::WebContents* web_contents = LaunchApp(SystemWebAppType::HELP);

  // There should be two browser windows, one regular and one for the newly
  // opened help app.
  EXPECT_EQ(2u, chrome::GetTotalBrowserCount());

  const GURL expected_url("chrome://os-settings/osPeople");
  content::TestNavigationObserver navigation_observer(expected_url);
  navigation_observer.StartWatchingNewWebContents();

  // Script that tells the Help App to show parental controls.
  constexpr char kScript[] = R"(
    (async () => {
      await window.customLaunchData.delegate.showParentalControls();
    })();
  )";
  // Trigger the script, then wait for settings to open. Use ExecJs
  // instead of EvalJsInAppFrame because the script needs to run in the same
  // world as the page's code.
  EXPECT_TRUE(content::ExecJs(
      SandboxedWebUiAppTestBase::GetAppFrame(web_contents), kScript));
  navigation_observer.Wait();

  // Settings should be active in a new window.
  EXPECT_EQ(3u, chrome::GetTotalBrowserCount());
  EXPECT_EQ(expected_url, GetActiveWebContents()->GetVisibleURL());
}

// Test that the Help App's `openUrlInBrowserAndTriggerInstallDialog` can open
// valid URLs if the `kHelpAppAutoTriggerInstallDialog` feature is disabled.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTestWithAutoTriggerDisabled,
                       HelpAppV2CanOpenValidHttpsUrlsInBrowser) {
  ASSERT_TRUE(https_server()->Start());
  const GURL test_url = https_server()->GetURL("/title1.html");

  ASSERT_TRUE(test_url.SchemeIs(url::kHttpsScheme));

  // There should be only be one regular browser with one tab.
  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
  EXPECT_EQ(1, browser()->tab_strip_model()->GetTabCount());

  WaitForTestSystemAppInstall();
  content::WebContents* web_contents = LaunchApp(SystemWebAppType::HELP);

  // There should be two browser windows, one regular and one for the newly
  // opened help app.
  EXPECT_EQ(2u, chrome::GetTotalBrowserCount());

  content::TestNavigationObserver navigation_observer(test_url);
  navigation_observer.StartWatchingNewWebContents();

  // Script that tells the Help App to call the
  // openUrlInBrowserAndTriggerInstallDialog Mojo function.
  constexpr char kScript[] = R"(
    (async () => {
      const delegate = window.customLaunchData.delegate;
      await delegate.openUrlInBrowserAndTriggerInstallDialog($1);
    })();
  )";
  // Trigger the script, then wait for the URL to open in a new tab. Use
  // ExecJs instead of EvalJsInAppFrame because the script needs to run
  // in the same world as the page's code.
  EXPECT_TRUE(
      content::ExecJs(SandboxedWebUiAppTestBase::GetAppFrame(web_contents),
                      content::JsReplace(kScript, test_url),
                      content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES));
  navigation_observer.Wait();

  // There should still be two browser windows.
  EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
  // The regular browser should only have 2 tabs.
  EXPECT_EQ(2, browser()->tab_strip_model()->GetTabCount());
  // After opening the URL, the regular browser should be the most recently
  // active browser.
  EXPECT_EQ(browser(), chrome::FindLastActive());
  // The active tab should be the `test_url` we opened.
  EXPECT_EQ(test_url, GetActiveWebContents()->GetVisibleURL());
}

// Test that the Help App's `openUrlInBrowserAndTriggerInstallDialog` navigates
// and triggers the install dialog by default.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest,
                       HelpAppV2CanTriggerInstallDialogForValidHttpsUrls) {
  if (web_app::IsWebAppsCrosapiEnabled()) {
    // TODO(b/282099820): Test the interaction with the Lacros browser.
    GTEST_SKIP();
  }
  ASSERT_TRUE(https_server()->Start());
  const GURL test_url =
      https_server()->GetURL("/banners/manifest_test_page.html");

  ASSERT_TRUE(test_url.SchemeIs(url::kHttpsScheme));

  // There should be only be one regular browser with one tab.
  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
  EXPECT_EQ(1, browser()->tab_strip_model()->GetTabCount());

  WaitForTestSystemAppInstall();
  content::WebContents* web_contents = LaunchApp(SystemWebAppType::HELP);

  // There should be two browser windows, one regular and one for the newly
  // opened help app.
  EXPECT_EQ(2u, chrome::GetTotalBrowserCount());

  content::TestNavigationObserver navigation_observer(test_url);
  navigation_observer.StartWatchingNewWebContents();

  std::string dialog_name =
      base::FeatureList::IsEnabled(::features::kWebAppUniversalInstall)
          ? "WebAppSimpleInstallDialog"
          : "PWAConfirmationBubbleView";
  views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey(),
                                       dialog_name);

  // Script that tells the Help App to call the
  // OpenUrlInBrowserAndTriggerInstallDialog Mojo function.
  constexpr char kScript[] = R"(
    (async () => {
      const delegate = window.customLaunchData.delegate;
      await delegate.openUrlInBrowserAndTriggerInstallDialog($1);
    })();
  )";
  // Trigger the script, then wait for the URL to open in a new tab. Use
  // ExecJs instead of EvalJsInAppFrame because the script needs to run in the
  // same world as the page's code.
  EXPECT_TRUE(
      content::ExecJs(SandboxedWebUiAppTestBase::GetAppFrame(web_contents),
                      content::JsReplace(kScript, test_url)));
  navigation_observer.Wait();

  // There should still be two browser windows.
  EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
  // The regular browser should only have 2 tabs.
  EXPECT_EQ(2, browser()->tab_strip_model()->GetTabCount());
  // After opening the URL, the regular browser should be the most recently
  // active browser.
  EXPECT_EQ(browser(), chrome::FindLastActive());
  // The active tab should be the `test_url` we opened.
  EXPECT_EQ(test_url, GetActiveWebContents()->GetVisibleURL());

  // Wait for the PWA install dialog to show up.
  views::Widget* widget = waiter.WaitIfNeededAndGet();
  ASSERT_NE(widget, nullptr);
}

// Test that the Help App's `openUrlInBrowserAndTriggerInstallDialog` crashes
// for invalid URLs.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest,
                       HelpAppV2CrashesForInvalidUrlsInBrowser) {
  // TODO(b/287166176): Fix the test and remove this.
  if (GetParam().crosapi_state == TestProfileParam::CrosapiParam::kEnabled) {
    GTEST_SKIP()
        << "Skipping test body for CrosapiParam::kEnabled, see b/287166176.";
  }

  // There should be only be one regular browser with one tab.
  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
  // The regular browser should only have 1 tab.
  EXPECT_EQ(1, browser()->tab_strip_model()->GetTabCount());
  // The tab should be the default "about:blank" URL.
  EXPECT_TRUE(GetActiveWebContents()->GetVisibleURL().IsAboutBlank());

  WaitForTestSystemAppInstall();

  // Script that tells the Help App to call the
  // `OpenUrlInBrowserAndTriggerInstallDialog` Mojo function.
  constexpr char kScript[] = R"(
    (async () => {
      const delegate = window.customLaunchData.delegate;
      await delegate.openUrlInBrowserAndTriggerInstallDialog($1);
    })();
  )";
  std::string invalid_urls[] = {"",
                                "test",
                                "www.test.com",
                                "http://test.com",
                                "data:,Hello%2C%20World%21",
                                "file:///home/foo.html",
                                "javascript:alert('Hello World')"};
  for (const std::string& test_url : invalid_urls) {
    // Launch a new Help app window per test URL.
    Browser* help_app_browser;
    content::WebContents* web_contents =
        LaunchApp(SystemWebAppType::HELP, &help_app_browser);

    // There should be two browser windows, one regular and one for the newly
    // opened help app.
    EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
    auto* frame = SandboxedWebUiAppTestBase::GetAppFrame(web_contents);

    // Test that calls with invalid URLs crash the renderer process.
    {
      content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
      content::RenderFrameDeletedObserver crash_observer(frame);

      content::ExecuteScriptAsync(frame, content::JsReplace(kScript, test_url));

      crash_observer.WaitUntilDeleted();
    }
    EXPECT_TRUE(web_contents->IsCrashed());

    // The Help app renderer process crashed. Close the browser window so that
    // we can relaunch it in another browser window.
    chrome::CloseWindow(help_app_browser);
    ui_test_utils::WaitForBrowserToClose(help_app_browser);

    // There should only be 1 regular browser.
    EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
    // The regular browser should still only have 1 tab.
    EXPECT_EQ(1, browser()->tab_strip_model()->GetTabCount());
    // The tab should still be the default "about:blank" URL.
    EXPECT_TRUE(GetActiveWebContents()->GetVisibleURL().IsAboutBlank());
  }
}

// Test that the Help App delegate can open the Microsoft 365 setup flow.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2CanOpenMS365Setup) {
  WaitForTestSystemAppInstall();
  content::WebContents* web_contents = LaunchApp(SystemWebAppType::HELP);

  // Script that tells the Help App to call the `LaunchMicrosoft365Setup` Mojo
  // function.
  constexpr char kScript[] = R"(
    (async () => {
      const delegate = window.customLaunchData.delegate;
      await delegate.launchMicrosoft365Setup();
      return true;
    })();
  )";
  EXPECT_EQ(true,
            content::EvalJs(
                SandboxedWebUiAppTestBase::GetAppFrame(web_contents), kScript));

  ash::SystemWebDialogDelegate* dialog =
      ash::SystemWebDialogDelegate::FindInstance(
          chrome::kChromeUICloudUploadURL);
  EXPECT_TRUE(dialog);
}

// Test that the Help App delegate can update the index for launcher search.
// Also test searching using the help app search handler.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest,
                       HelpAppV2UpdateLauncherSearchIndexAndSearch) {
  WaitForTestSystemAppInstall();
  content::WebContents* web_contents = LaunchApp(SystemWebAppType::HELP);

  // Script that adds a data item to the launcher search index.
  constexpr char kScript[] = R"(
    (async () => {
      const delegate = window.customLaunchData.delegate;
      await delegate.updateLauncherSearchIndex([{
        id: 'test-id',
        title: 'Title',
        mainCategoryName: 'Help',
        tags: ['verycomplicatedsearchquery'],
        urlPathWithParameters: 'help/sub/3399763/id/6318213',
        locale: ''
      }]);
      return true;
    })();
  )";

  // Use ExtractBool to make the script wait until the update completes.
  EXPECT_EQ(true,
            content::EvalJs(
                SandboxedWebUiAppTestBase::GetAppFrame(web_contents), kScript));

  // Search using the search handler to confirm that the update happened.
  base::RunLoop run_loop;
  help_app::HelpAppManagerFactory::GetForBrowserContext(profile())
      ->search_handler()
      ->Search(u"verycomplicatedsearchquery",
               /*max_num_results=*/3u,
               base::BindLambdaForTesting(
                   [&](std::vector<help_app::mojom::SearchResultPtr>
                           search_results) {
                     EXPECT_EQ(search_results.size(), 1u);
                     EXPECT_EQ(search_results[0]->id, "test-id");
                     EXPECT_EQ(search_results[0]->title, u"Title");
                     EXPECT_EQ(search_results[0]->main_category, u"Help");
                     EXPECT_EQ(search_results[0]->locale, "");
                     EXPECT_GT(search_results[0]->relevance_score, 0.01);
                     run_loop.QuitClosure().Run();
                   }));
  run_loop.Run();
}

// Test that the Help App delegate filters out invalid search concepts when
// updating the launcher search index.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest,
                       HelpAppV2UpdateLauncherSearchIndexFilterInvalid) {
  WaitForTestSystemAppInstall();
  base::HistogramTester histogram_tester;
  content::WebContents* web_contents = LaunchApp(SystemWebAppType::HELP);

  // Script that adds a data item to the launcher search index.
  constexpr char kScript[] = R"(
    (async () => {
      const delegate = window.customLaunchData.delegate;
      await delegate.updateLauncherSearchIndex([
        {
          id: '6318213',  // Fix connection problems.
          title: 'Article 1: Invalid',
          mainCategoryName: 'Help',
          tags: ['verycomplicatedsearchquery'],
          urlPathWithParameters: '',  // Invalid because empty field.
          locale: '',
        },
        {
          id: 'test-id-2',
          title: 'Article 2: Valid',
          mainCategoryName: 'Help',
          tags: ['verycomplicatedsearchquery'],
          urlPathWithParameters: 'help/',
          locale: '',
        },
        {
          id: '1700055',  // Open, save, or delete files.
          title: 'Article 3: Invalid',
          mainCategoryName: 'Help',
          tags: [''],  // Invalid because no non-empty tags.
          urlPathWithParameters: 'help/',
          locale: '',
        },
      ]);
      return true;
    })();
  )";

  // Use ExtractBool to make the script wait until the update completes.
  EXPECT_EQ(true,
            content::EvalJs(
                SandboxedWebUiAppTestBase::GetAppFrame(web_contents), kScript));

  // These hash values can be found in the enum in the google-internal histogram
  // file.
  histogram_tester.ExpectBucketCount(
      "Discover.LauncherSearch.InvalidConceptInUpdate", -20424143, 1);
  histogram_tester.ExpectBucketCount(
      "Discover.LauncherSearch.InvalidConceptInUpdate", 395626524, 1);

  // Search using the search handler to confirm that only the valid article was
  // added to the index.
  base::RunLoop run_loop;
  help_app::HelpAppManagerFactory::GetForBrowserContext(profile())
      ->search_handler()
      ->Search(u"verycomplicatedsearchquery",
               /*max_num_results=*/3u,
               base::BindLambdaForTesting(
                   [&](std::vector<help_app::mojom::SearchResultPtr>
                           search_results) {
                     EXPECT_EQ(search_results.size(), 1u);
                     EXPECT_EQ(search_results[0]->id, "test-id-2");
                     run_loop.QuitClosure().Run();
                   }));
  run_loop.Run();
}

// Test that the Help App background task works.
// It should open and update the index for launcher search, then close.
IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest,
                       HelpAppV2BackgroundTaskUpdatesLauncherSearchIndex) {
  WaitForTestSystemAppInstall();
  ui::ScopedSetIdleState idle(ui::IDLE_STATE_IDLE);

  const GURL bg_task_url("chrome://help-app/background");
  content::TestNavigationObserver navigation_observer(bg_task_url);
  navigation_observer.StartWatchingNewWebContents();

  // Wait for system apps background tasks to start.
  base::RunLoop run_loop;
  SystemWebAppManager::GetForTest(browser()->profile())
      ->on_tasks_started()
      .Post(FROM_HERE, run_loop.QuitClosure());
  run_loop.Run();

  auto& tasks = GetManager().GetBackgroundTasksForTesting();

  // Find the help app's background task.
  const auto& help_task = base::ranges::find(
      tasks, bg_task_url, &SystemWebAppBackgroundTask::url_for_testing);
  ASSERT_NE(help_task, tasks.end());

  auto* timer = help_task->get()->get_timer_for_testing();
  EXPECT_EQ(SystemWebAppBackgroundTask::INITIAL_WAIT,
            help_task->get()->get_state_for_testing());
  // The "Immediate" timer waits for several minutes, and it's hard to mock time
  // properly in a browser test, so just fire the timer now. We're not testing
  // that base::Timer works.
  timer->FireNow();

  // Wait for the task to launch the background page.
  navigation_observer.Wait();

  // Store web_content while the page is open.
  content::WebContents* web_contents =
      help_task->get()->web_contents_for_testing();
  // Wait until the background page closes.
  content::WebContentsDestroyedWatcher destroyed_watcher(web_contents);
  destroyed_watcher.Wait();

  EXPECT_EQ(help_task->get()->opened_count_for_testing(), 1u);

#if !BUILDFLAG(ENABLE_CROS_HELP_APP)
  // This part only works in non-branded builds because it uses fake data added
  // by the mock app.
  // Search using the search handler to confirm that the update happened.
  base::RunLoop search_run_loop;
  help_app::HelpAppManagerFactory::GetForBrowserContext(profile())
      ->search_handler()
      ->Search(u"verycomplicatedsearchquery",
               /*max_num_results=*/1u,
               base::BindLambdaForTesting(
                   [&](std::vector<help_app::mojom::SearchResultPtr>
                           search_results) {
                     ASSERT_EQ(search_results.size(), 1u);
                     EXPECT_EQ(search_results[0]->id, "mock-app-test-id");
                     EXPECT_EQ(search_results[0]->title, u"Title");
                     EXPECT_EQ(search_results[0]->main_category, u"Help");
                     EXPECT_EQ(search_results[0]->locale, "");
                     EXPECT_EQ(search_results[0]->url_path_with_parameters,
                               "help/sub/3399763/");
                     EXPECT_GT(search_results[0]->relevance_score, 0.01);
                     search_run_loop.QuitClosure().Run();
                   }));
  search_run_loop.Run();
#endif
}

IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2CanOpenAlmanacScheme) {
  WaitForTestSystemAppInstall();
  content::WebContents* web_contents = LaunchApp(SystemWebAppType::HELP);

  base::test::TestFuture<apps::PackageId> future;
  apps::AppInstallServiceAsh::InstallAppCallbackForTesting() =
      future.GetCallback();
  constexpr char kScript[] = R"(
    (() => {
      location.href = 'almanac://install-app?package_id=web:test';
      return true;
    })();
  )";
  EXPECT_EQ(true,
            content::EvalJs(
                SandboxedWebUiAppTestBase::GetAppFrame(web_contents), kScript));
  EXPECT_EQ(future.Get<apps::PackageId>(),
            apps::PackageId::FromString("web:test"));
}

IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2CanOpenCrosAppsScheme) {
  WaitForTestSystemAppInstall();
  content::WebContents* web_contents = LaunchApp(SystemWebAppType::HELP);

  base::test::TestFuture<apps::PackageId> future;
  apps::AppInstallServiceAsh::InstallAppCallbackForTesting() =
      future.GetCallback();
  constexpr char kScript[] = R"(
    (() => {
      location.href = 'cros-apps://install-app?package_id=web:test';
      return true;
    })();
  )";
  EXPECT_EQ(true,
            content::EvalJs(
                SandboxedWebUiAppTestBase::GetAppFrame(web_contents), kScript));
  EXPECT_EQ(future.Get<apps::PackageId>(),
            apps::PackageId::FromString("web:test"));
}

// Test that the Help App opens when Gesture help requested.
IN_PROC_BROWSER_TEST_P(HelpAppAllProfilesIntegrationTest, HelpAppOpenGestures) {
  WaitForTestSystemAppInstall();
  base::HistogramTester histogram_tester;

  GURL expected_url = GURL("chrome://help-app/help/sub/3399710/id/9739838");
  content::TestNavigationObserver navigation_observer(expected_url);
  navigation_observer.StartWatchingNewWebContents();
  SystemTrayClientImpl::Get()->ShowGestureEducationHelp();

  EXPECT_NO_FATAL_FAILURE(navigation_observer.Wait());
  EXPECT_EQ(expected_url, GetActiveWebContents()->GetVisibleURL());

  histogram_tester.ExpectUniqueSample("Apps.DefaultAppLaunch.FromOtherApp",
                                      apps::DefaultAppName::kHelpApp, 1);
  histogram_tester.ExpectUniqueSample("Discover.Overall.AppLaunched",
                                      apps::LaunchSource::kFromOtherApp, 1);
}

// Test that the Help App opens from keyboard shortcut.
IN_PROC_BROWSER_TEST_P(HelpAppAllProfilesIntegrationTest,
                       HelpAppOpenKeyboardShortcut) {
  // TODO(b/287166176): Fix the test and remove this.
#if !BUILDFLAG(GOOGLE_CHROME_BRANDING)
  if (GetParam().crosapi_state == TestProfileParam::CrosapiParam::kEnabled) {
    GTEST_SKIP()
        << "Skipping test body for CrosapiParam::kEnabled, see b/287166176.";
  }
#endif

  WaitForTestSystemAppInstall();
  base::HistogramTester histogram_tester;

  // The /? key is OEM_2 on a US standard keyboard.
  GURL expected_url;
#if BUILDFLAG(ENABLE_CROS_HELP_APP)
  expected_url = GURL("chrome://help-app");
#else
  expected_url = GURL(chrome::kChromeHelpViaKeyboardURL);
#endif
  content::TestNavigationObserver navigation_observer(expected_url);
  navigation_observer.StartWatchingNewWebContents();
  ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
      browser(), ui::VKEY_OEM_2, /*control=*/true,
      /*shift=*/false, /*alt=*/false, /*command=*/false));
  navigation_observer.Wait();

#if BUILDFLAG(ENABLE_CROS_HELP_APP)
  // Default browser tab and Help app are open.
  EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
  EXPECT_EQ("chrome://help-app/", GetActiveWebContents()->GetVisibleURL());
  histogram_tester.ExpectUniqueSample("Apps.DefaultAppLaunch.FromKeyboard",
                                      apps::DefaultAppName::kHelpApp, 1);
  histogram_tester.ExpectUniqueSample("Discover.Overall.AppLaunched",
                                      apps::LaunchSource::kFromKeyboard, 1);
#else
  // We just have the one browser. Navigates chrome.
  EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
  EXPECT_EQ(GURL(chrome::kChromeHelpViaKeyboardURL),
            GetActiveWebContents()->GetVisibleURL());
  histogram_tester.ExpectUniqueSample("Apps.DefaultAppLaunch.FromKeyboard",
                                      apps::DefaultAppName::kHelpApp, 0);
  histogram_tester.ExpectUniqueSample("Discover.Overall.AppLaunched",
                                      apps::LaunchSource::kFromKeyboard, 0);
#endif
}

// Test that the Help App opens in a new window if try to navigate there in a
// browser.
IN_PROC_BROWSER_TEST_P(HelpAppAllProfilesIntegrationTest,
                       HelpAppCapturesBrowserNavigation) {
  WaitForTestSystemAppInstall();
  content::TestNavigationObserver navigation_observer(
      GURL("chrome://help-app"));
  navigation_observer.StartWatchingNewWebContents();
  ASSERT_EQ(1u, chrome::GetTotalBrowserCount());

  // Try to navigate to the help app in the browser.
  ui_test_utils::SendToOmniboxAndSubmit(browser(), "chrome://help-app");
  navigation_observer.Wait();

  // We now have two browsers, one for the chrome window, one for the Help app.
  EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
  EXPECT_EQ(GURL("chrome://help-app"), GetActiveWebContents()->GetVisibleURL());
}

INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
    HelpAppIntegrationTest);

INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
    HelpAppIntegrationTestWithAutoTriggerDisabled);

INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
    HelpAppIntegrationTestWithFirstRunEnabled);

INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_PROFILE_TYPES_P(
    HelpAppAllProfilesIntegrationTest);

INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
    HelpAppIntegrationTestWithHelpAppOpensInsteadOfReleaseNotesNotification);

INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
    HelpAppIntegrationTestWithBirchFeatureEnabled);
}  // namespace ash