chromium/chrome/browser/ui/views/profiles/first_run_lacros_interactive_uitest.cc

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

#include "base/callback_list.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "chrome/browser/first_run/first_run.h"
#include "chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h"
#include "chrome/browser/search_engine_choice/search_engine_choice_dialog_service_factory.h"
#include "chrome/browser/signin/chrome_signin_client_test_util.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/browser/ui/startup/first_run_service.h"
#include "chrome/browser/ui/startup/first_run_test_util.h"
#include "chrome/browser/ui/views/profiles/profile_picker_interactive_uitest_base.h"
#include "chrome/browser/ui/views/profiles/profile_picker_view.h"
#include "chrome/browser/ui/webui/signin/signin_url_utils.h"
#include "chrome/test/interaction/interactive_browser_test.h"
#include "components/prefs/pref_service.h"
#include "components/search_engines/search_engines_pref_names.h"
#include "components/search_engines/search_engines_switches.h"
#include "content/public/test/browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/views/view_class_properties.h"

namespace {

DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kProfilePickerViewId);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebContentsId);
DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kButtonEnabled);
DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kButtonDisabled);

using DeepQuery = WebContentsInteractionTestUtil::DeepQuery;
const DeepQuery kProceedButton{"intro-app", "#proceedButton"};
const DeepQuery kOptInSyncButton{"sync-confirmation-app", "#confirmButton"};

struct TestParam {
  std::string test_suffix;
  bool with_search_engine_choice_step = false;
};

std::string ParamToTestSuffix(const ::testing::TestParamInfo<TestParam>& info) {
  return info.param.test_suffix;
}

// Permutations of supported parameters.
const TestParam kTestParams[] = {
    {.test_suffix = "Default"},
    {.test_suffix = "WithSearchEngineChoiceStep",
     .with_search_engine_choice_step = true},
};

}  // namespace

class FirstRunLacrosInteractiveUiTest
    : public InteractiveBrowserTestT<FirstRunServiceBrowserTestBase>,
      public WithProfilePickerInteractiveUiTestHelpers,
      public testing::WithParamInterface<TestParam> {
 public:
  FirstRunLacrosInteractiveUiTest() {
    std::vector<base::test::FeatureRef> enabled_features;
    std::vector<base::test::FeatureRef> disabled_features;

    if (WithSearchEngineChoiceStep()) {
      scoped_chrome_build_override_ = std::make_unique<base::AutoReset<bool>>(
          SearchEngineChoiceDialogServiceFactory::
              ScopedChromeBuildOverrideForTesting(
                  /*force_chrome_build=*/true));

      enabled_features.push_back(switches::kSearchEngineChoiceTrigger);
    } else {
      disabled_features.push_back(switches::kSearchEngineChoiceTrigger);
    }

    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
  }

  // FirstRunInteractiveUiTestBase:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    FirstRunServiceBrowserTestBase::SetUpCommandLine(command_line);

    // Change the country to Belgium because the search engine choice screen is
    // only displayed for EEA countries.
    command_line->AppendSwitchASCII(switches::kSearchEngineChoiceCountry, "BE");

    command_line->AppendSwitch(
        switches::kIgnoreNoFirstRunForSearchEngineChoiceScreen);
  }

  void TearDownOnMainThread() override {
    FirstRunServiceBrowserTestBase::TearDownOnMainThread();
    identity_test_env_adaptor_.reset();
  }

  void SetUpOnMainThread() override {
    FirstRunServiceBrowserTestBase::SetUpOnMainThread();

    if (WithSearchEngineChoiceStep()) {
      SearchEngineChoiceDialogService::SetDialogDisabledForTests(
          /*dialog_disabled=*/false);
    }

    identity_test_env_adaptor_ =
        std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile());
    identity_test_env_adaptor_->identity_test_env()
        ->SetRefreshTokenForPrimaryAccount();
  }

  void SetUpInProcessBrowserTestFixture() override {
    create_services_subscription_ =
        BrowserContextDependencyManager::GetInstance()
            ->RegisterCreateServicesCallbackForTesting(
                base::BindRepeating(&FirstRunLacrosInteractiveUiTest::
                                        OnWillCreateBrowserContextServices,
                                    base::Unretained(this)));
  }

  void OpenFirstRun(base::OnceCallback<void(bool)> first_run_exited_callback =
                        base::OnceCallback<void(bool)>()) {
    ASSERT_TRUE(fre_service()->ShouldOpenFirstRun());

    fre_service()->OpenFirstRunIfNeeded(FirstRunService::EntryPoint::kOther,
                                        std::move(first_run_exited_callback));

    WaitForPickerWidgetCreated();
    view()->SetProperty(views::kElementIdentifierKey, kProfilePickerViewId);
  }

  bool WithSearchEngineChoiceStep() const {
    return GetParam().with_search_engine_choice_step;
  }

  auto PressJsButton(const ui::ElementIdentifier web_contents_id,
                     const DeepQuery& button_query) {
    // This can close/navigate the current page, so don't wait for success.
    return ExecuteJsAt(web_contents_id, button_query, "(btn) => btn.click()",
                       ExecuteJsMode::kFireAndForget);
  }

  auto WaitForButtonEnabled(const ui::ElementIdentifier web_contents_id,
                            const DeepQuery& button_query) {
    StateChange button_enabled;
    button_enabled.event = kButtonEnabled;
    button_enabled.where = button_query;
    button_enabled.type = StateChange::Type::kExistsAndConditionTrue;
    button_enabled.test_function = "(btn) => !btn.disabled";
    return WaitForStateChange(web_contents_id, button_enabled);
  }

  auto WaitForButtonDisabled(const ui::ElementIdentifier web_contents_id,
                             const DeepQuery& button_query) {
    StateChange button_disabled;
    button_disabled.event = kButtonDisabled;
    button_disabled.where = button_query;
    button_disabled.type = StateChange::Type::kExistsAndConditionTrue;
    button_disabled.test_function = "(btn) => btn.disabled";
    return WaitForStateChange(web_contents_id, button_disabled);
  }

  auto CompleteSearchEngineChoiceStep() {
    const DeepQuery kFirstSearchEngine = {"search-engine-choice-app",
                                          "cr-radio-button"};
    const DeepQuery kSearchEngineChoiceActionButton = {
        "search-engine-choice-app", "#actionButton"};
    return Steps(
        WaitForWebContentsNavigation(
            kWebContentsId, GURL(chrome::kChromeUISearchEngineChoiceURL)),
        // Click on "More" to scroll to the bottom of the search engine list.
        PressJsButton(kWebContentsId, kSearchEngineChoiceActionButton),
        // The button should become disabled because we didn't make a choice.
        WaitForButtonDisabled(kWebContentsId, kSearchEngineChoiceActionButton),
        PressJsButton(kWebContentsId, kFirstSearchEngine),
        WaitForButtonEnabled(kWebContentsId, kSearchEngineChoiceActionButton),
        PressJsButton(kWebContentsId, kSearchEngineChoiceActionButton));
  }

  const base::HistogramTester& HistogramTester() const {
    return histogram_tester_;
  }

 protected:
  void OnWillCreateBrowserContextServices(content::BrowserContext* context) {
    IdentityTestEnvironmentProfileAdaptor::
        SetIdentityTestEnvironmentFactoriesOnBrowserContext(context);
  }

 private:
  std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
      identity_test_env_adaptor_;
  base::CallbackListSubscription create_services_subscription_;
  base::test::ScopedFeatureList scoped_feature_list_;
  base::HistogramTester histogram_tester_;
  std::unique_ptr<base::AutoReset<bool>> scoped_chrome_build_override_;
};

INSTANTIATE_TEST_SUITE_P(,
                         FirstRunLacrosInteractiveUiTest,
                         testing::ValuesIn(kTestParams),
                         &ParamToTestSuffix);

IN_PROC_BROWSER_TEST_P(FirstRunLacrosInteractiveUiTest,
                       AcceptSyncAndFinishFlow) {
  base::test::TestFuture<bool> proceed_future;
  ASSERT_TRUE(IsProfileNameDefault());
  OpenFirstRun(proceed_future.GetCallback());
  GURL sync_page_url = AppendSyncConfirmationQueryParams(
      GURL("chrome://sync-confirmation/"), SyncConfirmationStyle::kWindow,
      /*is_sync_promo=*/true);

  RunTestSequenceInContext(
      views::ElementTrackerViews::GetContextForView(view()),

      // Wait for the profile picker to show the intro.
      WaitForShow(kProfilePickerViewId),
      InstrumentNonTabWebView(kWebContentsId, web_view()),
      WaitForWebContentsReady(kWebContentsId, GURL(chrome::kChromeUIIntroURL)),

      // Advance to the sync confirmation page.
      PressJsButton(kWebContentsId, kProceedButton),
      WaitForWebContentsNavigation(kWebContentsId, sync_page_url),

      // Accept sync.
      EnsurePresent(kWebContentsId, kOptInSyncButton),
      PressJsButton(kWebContentsId, kOptInSyncButton)
          .SetMustRemainVisible(false),

      If([&] { return WithSearchEngineChoiceStep(); },
         CompleteSearchEngineChoiceStep()));

  WaitForPickerClosed();

  if (WithSearchEngineChoiceStep()) {
    HistogramTester().ExpectBucketCount(
        search_engines::kSearchEngineChoiceScreenEventsHistogram,
        search_engines::SearchEngineChoiceScreenEvents::kFreDefaultWasSet, 1);
    PrefService* pref_service = browser()->profile()->GetPrefs();
    EXPECT_TRUE(pref_service->HasPrefPath(
        prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp));
  }

  EXPECT_TRUE(GetFirstRunFinishedPrefValue());
  EXPECT_FALSE(fre_service()->ShouldOpenFirstRun());
  EXPECT_FALSE(IsUsingDefaultProfileName());
}