chromium/chrome/browser/ui/views/side_panel/search_companion/companion_page_browsertest.cc

// Copyright 2023 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/base64url.h"
#include "base/base_paths.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_samples.h"
#include "base/metrics/statistics_recorder.h"
#include "base/path_service.h"
#include "base/strings/escape.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "build/buildflag.h"
#include "chrome/browser/companion/core/companion_metrics_logger.h"
#include "chrome/browser/companion/core/constants.h"
#include "chrome/browser/companion/core/features.h"
#include "chrome/browser/companion/core/mojom/companion.mojom.h"
#include "chrome/browser/companion/core/proto/companion_url_params.pb.h"
#include "chrome/browser/companion/visual_query/visual_query_classifier_host.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/translate/chrome_translate_client.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_actions.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/side_panel/companion/companion_tab_helper.h"
#include "chrome/browser/ui/views/side_panel/companion/companion_utils.h"
#include "chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.h"
#include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
#include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
#include "chrome/browser/ui/views/side_panel/side_panel_enums.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/common/companion/visual_query/features.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/lens/lens_features.h"
#include "components/optimization_guide/core/test_model_info_builder.h"
#include "components/optimization_guide/core/test_optimization_guide_model_provider.h"
#include "components/optimization_guide/proto/visual_search_model_metadata.pb.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "components/translate/core/browser/language_state.h"
#include "components/translate/core/browser/translate_manager.h"
#include "components/ukm/test_ukm_recorder.h"
#include "components/unified_consent/pref_names.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "net/base/url_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/actions/actions.h"
#include "ui/base/ui_base_features.h"

#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/browser/extensions/tab_helper.h"
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

using side_panel::mojom::LoadingState;
using side_panel::mojom::MethodType;
using side_panel::mojom::PromoAction;
using side_panel::mojom::PromoType;
using side_panel::mojom::UiSurface;

using optimization_guide::proto::Any;
using optimization_guide::proto::EligibilitySpec;
using optimization_guide::proto::FeatureLibrary;
using optimization_guide::proto::OrOfThresholdingRules;
using optimization_guide::proto::ThresholdingRule;

namespace {

const char kRelativeUrl1[] = "/english_page.html";
const char kRelativeUrl2[] = "/german_page.html";
const char kRelativeUrl3[] = "/simple.html";
const char kRelativeUrl4[] = "/simple.html#part1";
const char kHost[] = "foo.com";
const char kSearchQueryUrl[] = "https://www.google.com/search?q=xyz";

const char kExpectedExpsPromoUrl[] = "https://foobar.com/";
const char kPhReportingUrl[] = "https://foobar.com/";
const char kExpsRegistrationSuccessUrl[] = "https://foobar.com/experiments";

const char kRelativeVisualQueryUrl[] = "/test_visual.html";

const char kExpectedNewTabLinkMetadata[] =
    "{\"openAction\":1,\"isSearchCompanionPinnedByDefault\":false}";
const char kExpectedClobberLinkMetadata[] =
    "{\"openAction\":2,\"isSearchCompanionPinnedByDefault\":false}";
const char kExpectedSearchUrlLinkMetadata[] =
    "{\"openAction\":3,\"isSearchCompanionPinnedByDefault\":false}";

base::FilePath model_file_path() {
  base::FilePath source_root_dir;
  base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &source_root_dir);
  return source_root_dir.AppendASCII("chrome")
      .AppendASCII("test")
      .AppendASCII("data")
      .AppendASCII("companion_visual_query")
      .AppendASCII("test-model-quantized.tflite");
}

optimization_guide::proto::Any model_metadata() {
  EligibilitySpec eligibility_spec;
  auto* new_rule = eligibility_spec.add_cheap_pruning_rules()->add_rules();
  new_rule->set_feature_name(FeatureLibrary::IMAGE_VISIBLE_AREA);
  new_rule->set_normalizing_op(FeatureLibrary::BY_VIEWPORT_AREA);
  new_rule->set_thresholding_op(FeatureLibrary::GT);
  new_rule->set_threshold(0.01);
  auto* shopping_rule =
      eligibility_spec.add_classifier_score_rules()->add_rules();
  shopping_rule->set_feature_name(FeatureLibrary::SHOPPING_CLASSIFIER_SCORE);
  shopping_rule->set_thresholding_op(FeatureLibrary::GT);
  shopping_rule->set_threshold(0.5);

  optimization_guide::proto::Any any_metadata;
  any_metadata.set_type_url(
      "type.googleapis.com/com.foo.VisualSearchModelMetadata");
  optimization_guide::proto::VisualSearchModelMetadata
      visual_search_model_metadata;
  visual_search_model_metadata.mutable_eligibility_spec()->MergeFrom(
      eligibility_spec);
  visual_search_model_metadata.SerializeToString(any_metadata.mutable_value());
  return any_metadata;
}

}  // namespace

// Helper class to generate a script that sends a postmessage to the browser
// with given parameters.
struct CompanionScriptBuilder {
  // Only mandatory argument.
  MethodType method_type;

  // The rest of the arguments are optional. If the value is set, will be added
  // to the postmessage.
  std::optional<PromoType> promo_type;
  std::optional<PromoAction> promo_action;
  std::optional<PhFeedback> ph_feedback;
  std::optional<bool> is_exps_opted_in;
  std::optional<std::string> url_for_open_in_new_tab;
  std::optional<std::string> url_to_open;
  std::optional<bool> use_new_tab;
  std::optional<UiSurface> ui_surface;
  std::optional<int> ui_surface_position;
  std::optional<int> child_element_available_count;
  std::optional<int> child_element_shown_count;
  std::optional<std::string> text_directive;
  std::optional<std::vector<std::string>> cq_text_directives;
  std::optional<int> click_position;
  std::optional<LoadingState> loading_state;

  // Useful in case chrome sends a postmessage in response. Companion waits for
  // the message in response and resolves the promise that was sent back to
  // EvalJs.
  bool wait_for_message = false;

  // Constructor.
  explicit CompanionScriptBuilder(MethodType type) : method_type(type) {}

  // Generates the JS script that can be injected to simulate a postmessage.
  std::string Build() {
    std::stringstream ss;
    ss << "const message = {};";
    ss << "message['type'] = "
       << base::NumberToString(static_cast<size_t>(method_type)) << ";";

    if (promo_type.has_value()) {
      ss << "message['promoType'] = "
         << base::NumberToString(static_cast<size_t>(promo_type.value()))
         << ";";
    }

    if (promo_action.has_value()) {
      ss << "message['promoAction'] = "
         << base::NumberToString(static_cast<size_t>(promo_action.value()))
         << ";";
    }

    if (ph_feedback.has_value()) {
      ss << "message['phFeedback'] = "
         << base::NumberToString(static_cast<size_t>(ph_feedback.value()))
         << ";";
    }

    if (is_exps_opted_in.has_value()) {
      ss << "message['isExpsOptedIn'] = "
         << base::NumberToString(is_exps_opted_in.value()) << ";";
    }

    if (url_for_open_in_new_tab.has_value()) {
      ss << "message['urlForOpenInNewTab'] = '"
         << url_for_open_in_new_tab.value() << "';";
    }

    if (url_to_open.has_value()) {
      ss << "message['urlToOpen'] = '" << url_to_open.value() << "';";
    }

    if (use_new_tab.has_value()) {
      ss << "message['useNewTab'] = '"
         << base::NumberToString(use_new_tab.value()) << "';";
    }

    if (ui_surface.has_value()) {
      ss << "message['uiSurface'] = "
         << base::NumberToString(static_cast<size_t>(ui_surface.value()))
         << ";";
    }

    if (ui_surface_position.has_value()) {
      ss << "message['uiSurfacePosition'] = "
         << base::NumberToString(ui_surface_position.value()) << ";";
    }

    if (child_element_available_count.has_value()) {
      ss << "message['childElementAvailableCount'] = "
         << base::NumberToString(child_element_available_count.value()) << ";";
    }

    if (child_element_shown_count.has_value()) {
      ss << "message['childElementShownCount'] = "
         << base::NumberToString(child_element_shown_count.value()) << ";";
    }

    if (text_directive.has_value()) {
      ss << "message['cqJumptagText'] = '" << text_directive.value() << "';";
    }

    if (cq_text_directives.has_value()) {
      std::string joined_text;
      for (const auto& text : cq_text_directives.value()) {
        joined_text.append("'" + text + "',");
      }
      ss << "message['cqTextDirectives'] = [" << joined_text << "];";
    }

    if (click_position.has_value()) {
      ss << "message['clickPosition'] = "
         << base::NumberToString(click_position.value()) << ";";
    }

    if (loading_state.has_value()) {
      ss << "message['companionLoadingState'] = "
         << base::NumberToString(static_cast<size_t>(loading_state.value()))
         << ";";
    }
    ss << "window.parent.postMessage(message, '*');";

    if (wait_for_message) {
      ss << "waitForMessage();";
    }

    return ss.str();
  }
};

class CompanionPageBrowserTest : public InProcessBrowserTest {
 protected:
  void SetUp() override {
    page_url_server_.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
    companion_server_.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
    vqs_url_server_.ServeFilesFromSourceDirectory(GetChromeTestDataDir());

    // Register a handler to inspect the URL and examine the proto.
    // Nevertheless, it returns null which causes the default handler to be
    // invoked right away.
    companion_server_.RegisterRequestHandler(base::BindRepeating(
        &CompanionPageBrowserTest::InspectRequest, base::Unretained(this)));

    ASSERT_TRUE(page_url_server_.Start());
    ASSERT_TRUE(companion_server_.Start());
    ASSERT_TRUE(vqs_url_server_.Start());
    SetUpFeatureList();
    histogram_tester_ = std::make_unique<base::HistogramTester>();
    InProcessBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
  }

  GURL CreateUrl(const std::string& host, const std::string& relative_url) {
    return page_url_server_.GetURL(host, relative_url);
  }

  content::WebContents* web_contents() {
    return browser()->tab_strip_model()->GetActiveWebContents();
  }

  SidePanelCoordinator* side_panel_coordinator() {
    return browser()->GetFeatures().side_panel_coordinator();
  }

  SearchCompanionSidePanelCoordinator*
  search_companion_side_panel_coordinator() {
    return SearchCompanionSidePanelCoordinator::GetOrCreateForBrowser(
        browser());
  }

  content::WebContents* GetCompanionWebContents(Browser* browser) {
    auto* companion_helper =
        companion::CompanionTabHelper::FromWebContents(web_contents());
    auto* web_contents = companion_helper->GetCompanionWebContentsForTesting();
    DCHECK(web_contents);
    return web_contents;
  }

  void WaitForCompanionToBeLoaded() {
    content::WebContents* companion_web_contents =
        GetCompanionWebContents(browser());
    EXPECT_TRUE(companion_web_contents);

    // Verify that extensions do not have access to the companion web contents.
#if BUILDFLAG(ENABLE_EXTENSIONS)
    CHECK_EQ(nullptr,
             extensions::TabHelper::FromWebContents(companion_web_contents));
#endif

    // Wait for the navigations in both the frames to complete.
    content::TestNavigationObserver nav_observer(companion_web_contents, 2);
    nav_observer.Wait();
  }

  void WaitForMainPageToBeLoaded(const std::string& relative_url) {
    // Wait for the navigations in the frame to complete.

    ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
                                             CreateUrl(kHost, relative_url)));
  }

  void WaitForCompanionIframeReload() {
    content::WebContents* companion_web_contents =
        GetCompanionWebContents(browser());
    EXPECT_TRUE(companion_web_contents);

    // Wait for the navigations in the inner iframe to complete.
    content::TestNavigationObserver nav_observer(companion_web_contents, 1);
    nav_observer.Wait();
  }

  // Mimics a user clicking a link to `url` in search companion and waits for
  // the page to load.
  void ClickUrlInCompanion(const GURL& url,
                           bool wait_for_navigation = true,
                           bool wait_for_message = false,
                           bool click_in_nested_iframe = false) {
    std::string waitForMessage = wait_for_message ? "waitForMessage();" : "";
    std::string script =
        "const link = document.createElement('a');link.target = "
        "\"blank_\";link.href=\"" +
        url.spec() + "\";document.body.appendChild(link);link.click();" +
        waitForMessage;
    if (click_in_nested_iframe) {
      ExecJsInNestedIframe(script);
    } else {
      ExecJs(script);
    }
    if (wait_for_navigation) {
      content::TestNavigationObserver nav_observer(web_contents());
      nav_observer.Wait();
    }
  }

  // Clicks a link to `url` in search companion, waits for it to open in the
  // main page, then redirects the main page to `redirect_url`.
  void ClickUrlInCompanionWithRedirect(const GURL& clicked_url,
                                       const GURL& redirect_url) {
    ClickUrlInCompanion(clicked_url);
    content::TestNavigationObserver nav_observer(web_contents());
    ExecJsInMainPage("location.replace('" + redirect_url.spec() + "');");
    nav_observer.Wait();
  }

  // Mimics a user clicking a link in the main page to `url` that opens a new
  // tab
  void ClickNewTabUrlInMainPage(const GURL& url) {
    std::string script =
        "const link = document.createElement('a');link.target = "
        "\"blank_\";link.href=\"" +
        url.spec() + "\";document.body.appendChild(link);link.click();";
    ExecJsInMainPage(script);
  }

  // Mimics pressing the back arrow
  void PressBackButton() {
    content::TestNavigationObserver nav_observer(web_contents());
    web_contents()->GetController().GoBack();
    nav_observer.Wait();
  }

  ::testing::AssertionResult ExecJs(const std::string& code) {
    // Execute test in iframe.
    content::RenderFrameHost* iframe =
        content::ChildFrameAt(GetCompanionWebContents(browser()), 0);

    return content::ExecJs(iframe, code);
  }

  ::testing::AssertionResult ExecJsInNestedIframe(const std::string& code) {
    // Create a nested iframe and execute test in it.
    content::RenderFrameHost* iframe =
        content::ChildFrameAt(GetCompanionWebContents(browser()), 0);
    std::string createIframeScript =
        "const frame = document.createElement('iframe');"
        "document.body.appendChild(frame);";
    // The return value is not required for this portion.
    std::ignore = content::ExecJs(iframe, createIframeScript);
    content::RenderFrameHost* nested_iframe = content::ChildFrameAt(iframe, 0);

    return content::ExecJs(nested_iframe, code);
  }

  // Executes Javascript in the active tab.
  ::testing::AssertionResult ExecJsInMainPage(const std::string& code) {
    return content::ExecJs(web_contents()->GetPrimaryMainFrame(), code);
  }

  content::EvalJsResult EvalJs(const std::string& code) {
    // Execute test in iframe.
    content::RenderFrameHost* iframe =
        content::ChildFrameAt(GetCompanionWebContents(browser()), 0);

    return content::EvalJs(iframe, code);
  }

  std::unique_ptr<net::test_server::HttpResponse> InspectRequest(
      const net::test_server::HttpRequest& request) {
    requests_received_on_server_++;
    const GURL& url = request.GetURL();

    std::string query_proto;
    net::GetValueForKeyInQuery(url, "companion_query", &query_proto);
    last_proto_from_url_load_ = DeserializeCompanionRequest(query_proto);

    if (request.method == net::test_server::HttpMethod::METHOD_POST) {
      net::GetValueForKeyInQuery(url, "sourcelang", &last_sourcelang_);
      net::GetValueForKeyInQuery(url, "targetlang", &last_targetlang_);
      net::GetValueForKeyInQuery(url, "vpw", &last_viewport_width_param_);
      net::GetValueForKeyInQuery(url, "vph", &last_viewport_height_param_);
    }
    return nullptr;
  }

  std::optional<companion::proto::CompanionUrlParams>
  GetLastCompanionProtoFromUrlLoad() {
    auto proto_copy = last_proto_from_url_load_;
    last_proto_from_url_load_ = std::nullopt;
    return proto_copy;
  }

  std::string GetLastSourceLang() { return last_sourcelang_; }

  std::string GetLastTargetLang() { return last_targetlang_; }

  int GetLastViewportWidthParam() {
    if (last_viewport_width_param_.empty()) {
      return 0;
    }

    int viewport_width;
    base::StringToInt(last_viewport_width_param_, &viewport_width);
    return viewport_width;
  }

  int GetLastViewportHeightParam() {
    if (last_viewport_height_param_.empty()) {
      return 0;
    }

    int viewport_height;
    base::StringToInt(last_viewport_height_param_, &viewport_height);
    return viewport_height;
  }

  companion::proto::CompanionUrlParams DeserializeCompanionRequest(
      const std::string& companion_url_param) {
    std::string serialized_proto;
    EXPECT_TRUE(base::Base64UrlDecode(
        companion_url_param, base::Base64UrlDecodePolicy::DISALLOW_PADDING,
        &serialized_proto));

    companion::proto::CompanionUrlParams proto;
    EXPECT_TRUE(proto.ParseFromString(serialized_proto));
    return proto;
  }

  std::optional<companion::proto::CompanionUrlParams>
  GetLastCompanionProtoFromPostMessage() {
    companion::proto::CompanionUrlParams proto;
    content::EvalJsResult eval_js_result =
        EvalJs("getLastReceivedCompanionProto()");
    if (!eval_js_result.error.empty() || !eval_js_result.value.is_string()) {
      return std::nullopt;
    }

    std::string companion_proto_encoded = eval_js_result.ExtractString();
    proto = DeserializeCompanionRequest(companion_proto_encoded);
    return proto;
  }

  std::optional<GURL> GetLastLinkOpenedUrlFromPostMessage() {
    content::EvalJsResult eval_js_result =
        EvalJs("getLastReceivedLinkOpenedUrl()");
    if (!eval_js_result.error.empty() || !eval_js_result.value.is_string()) {
      return std::nullopt;
    }
    return GURL(eval_js_result.ExtractString());
  }

  std::optional<std::string> GetLastLinkOpenedMetadataFromPostMessage() {
    content::EvalJsResult eval_js_result =
        EvalJs("getLastReceivedLinkOpenedMetadata()");
    if (!eval_js_result.error.empty() || !eval_js_result.value.is_string()) {
      return std::nullopt;
    }
    return eval_js_result.ExtractString();
  }

  std::optional<std::string> GetLastPageTitleFromPostMessage() {
    content::EvalJsResult eval_js_result = EvalJs("getLastReceivedPageTitle()");
    if (!eval_js_result.error.empty() || !eval_js_result.value.is_string()) {
      return std::nullopt;
    }
    return eval_js_result.ExtractString();
  }

  std::optional<std::string> GetLastInnerHtmlFromPostMessage() {
    content::EvalJsResult eval_js_result = EvalJs("getLastReceivedInnerHtml()");
    if (!eval_js_result.error.empty() || !eval_js_result.value.is_string()) {
      return std::nullopt;
    }
    return eval_js_result.ExtractString();
  }

  void EnableMsbb(bool enable_msbb) {
    auto* pref_service = browser()->profile()->GetPrefs();
    pref_service->SetBoolean(
        unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled,
        enable_msbb);
  }

  void EnableSignInMsbbExps(bool signed_in, bool msbb, bool exps) {
    if (signed_in) {
      // Mock a signed-in user.
      signin::SetPrimaryAccount(
          IdentityManagerFactory::GetForProfile(browser()->profile()),
          "[email protected]", signin::ConsentLevel::kSignin);
    }

    // Set MSBB and exps status.
    EnableMsbb(msbb);
    browser()->profile()->GetPrefs()->SetBoolean(
        companion::kExpsOptInStatusGrantedPref, exps);
  }

  void EnablePco(bool enable_pco) {
    auto* pref_service = browser()->profile()->GetPrefs();
    pref_service->SetBoolean(
        unified_consent::prefs::kPageContentCollectionEnabled, enable_pco);
  }

  virtual void SetUpFeatureList() {
    iph_feature_list_.InitAndEnableFeatures(
        {feature_engagement::kIPHCompanionSidePanelRegionSearchFeature});
    base::FieldTrialParams params;
    params["companion-homepage-url"] =
        companion_server_.GetURL("/companion_iframe.html").spec();
    params["companion-image-upload-url"] =
        companion_server_.GetURL("/upload").spec();
    params["open-links-in-current-tab"] = ShouldOpenLinkInCurrentTab();
    params["open-companion-for-image-search"] =
        enable_feature_companion_image_search_;
    base::FieldTrialParams params2;
    params2["exps-registration-success-page-urls"] =
        kExpsRegistrationSuccessUrl;
    params2["companion-homepage-url"] =
        companion_server_.GetURL("/companion_iframe.html").spec();
    params2["companion-image-upload-url"] =
        companion_server_.GetURL("/upload").spec();
    params2["open-companion-for-image-search"] =
        enable_feature_companion_image_search_;
    base::FieldTrialParams standalone_params;
    standalone_params["lens-homepage-url"] =
        companion_server_.GetURL("/upload").spec();

    std::vector<base::test::FeatureRefAndParams> enabled_features;
    std::vector<base::test::FeatureRef> disabled_features;
    if (enable_feature_lens_standalone_) {
      enabled_features.emplace_back(lens::features::kLensStandalone,
                                    /*params=*/standalone_params);
    } else {
      disabled_features.emplace_back(lens::features::kLensStandalone);
    }

    if (enable_feature_side_panel_companion_) {
      enabled_features.emplace_back(
          companion::features::internal::kSidePanelCompanion, params);
    } else {
      disabled_features.emplace_back(
          companion::features::internal::kSidePanelCompanion);
    }

    if (enable_feature_visual_query_) {
      enabled_features.emplace_back(base::test::FeatureRefAndParams(
          companion::visual_query::features::kVisualQuerySuggestions,
          /*params*/ {}));
    }
    enabled_features.emplace_back(base::test::FeatureRefAndParams(
        companion::features::kCompanionEnablePageContent, /*params*/ {}));
    enabled_features.emplace_back(
        companion::features::internal::
            kCompanionEnabledByObservingExpsNavigations,
        params2);
    disabled_features.emplace_back(lens::features::kLensOverlay);

    feature_list_.InitWithFeaturesAndParameters(enabled_features,
                                                disabled_features);
  }

  virtual std::string ShouldOpenLinkInCurrentTab() { return "false"; }

  void WaitForTabCount(int expected) {
    while (browser()->tab_strip_model()->count() != expected) {
      base::RunLoop().RunUntilIdle();
    }
  }

  void WaitForSidePanelEntryShowing(SidePanelEntry::Id expected) {
    while (side_panel_coordinator()->GetCurrentEntryId() != expected) {
      base::RunLoop().RunUntilIdle();
    }
  }

  void WaitForHistogram(const std::string& histogram_name) {
    // Continue if histogram was already recorded.
    if (base::StatisticsRecorder::FindHistogram(histogram_name)) {
      return;
    }

    // Else, wait until the histogram is recorded.
    base::RunLoop run_loop;
    auto histogram_observer = std::make_unique<
        base::StatisticsRecorder::ScopedHistogramSampleObserver>(
        histogram_name,
        base::BindLambdaForTesting(
            [&](const char* histogram_name, uint64_t name_hash,
                base::HistogramBase::Sample sample) { run_loop.Quit(); }));
    run_loop.Run();
  }

  void ExpectUkmCount(ukm::TestUkmRecorder* ukm_recorder,
                      size_t expected_count) {
    EXPECT_EQ(
        expected_count,
        ukm_recorder
            ->GetEntriesByName(ukm::builders::Companion_PageView::kEntryName)
            .size());
  }

  void ExpectUkmEntry(ukm::TestUkmRecorder* ukm_recorder,
                      const char* metric_name,
                      int expected_value) {
    ExpectUkmCount(ukm_recorder, 1u);
    ExpectUkmEntryAt(ukm_recorder, 0, metric_name, expected_value);
  }

  void ExpectUkmEntryAt(ukm::TestUkmRecorder* ukm_recorder,
                        int index,
                        const char* metric_name,
                        int expected_value) {
    const char* entry_name = ukm::builders::Companion_PageView::kEntryName;
    EXPECT_LE(index, static_cast<int>(
                         ukm_recorder->GetEntriesByName(entry_name).size()));
    auto* entry = ukm_recorder->GetEntriesByName(entry_name)[index].get();

    // Verify the metric.
    ukm_recorder->EntryHasMetric(entry, metric_name);
    ukm_recorder->ExpectEntryMetric(entry, metric_name, expected_value);
  }

  void ExpectUkmEntryAtIndexToNotHaveMetric(ukm::TestUkmRecorder* ukm_recorder,
                                            int index,
                                            const char* metric_name) {
    const char* entry_name = ukm::builders::Companion_PageView::kEntryName;
    EXPECT_LE(index, static_cast<int>(
                         ukm_recorder->GetEntriesByName(entry_name).size()));
    auto* entry = ukm_recorder->GetEntriesByName(entry_name)[index].get();

    EXPECT_FALSE(ukm_recorder->EntryHasMetric(entry, metric_name));
  }

  size_t requests_received_on_server() const {
    return requests_received_on_server_;
  }

 protected:
  feature_engagement::test::ScopedIphFeatureList iph_feature_list_;
  base::test::ScopedFeatureList feature_list_;
  net::EmbeddedTestServer page_url_server_{net::EmbeddedTestServer::TYPE_HTTPS};
  net::EmbeddedTestServer companion_server_{net::EmbeddedTestServer::TYPE_HTTP};
  net::EmbeddedTestServer vqs_url_server_{net::EmbeddedTestServer::TYPE_HTTP};
  std::unique_ptr<base::HistogramTester> histogram_tester_;
  std::optional<companion::proto::CompanionUrlParams> last_proto_from_url_load_;
  size_t requests_received_on_server_ = 0;
  std::string last_sourcelang_;
  std::string last_targetlang_;
  std::string last_viewport_width_param_;
  std::string last_viewport_height_param_;
  std::string enable_feature_companion_image_search_ = "true";
  bool enable_feature_side_panel_companion_ = true;
  bool enable_feature_visual_query_ = true;
  bool enable_feature_lens_standalone_ = true;
};

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest, InitialNavigationWithoutMsbb) {
  // Turn off Msbb. Load a page on the active tab and open companion side panel.
  EnableMsbb(false);
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);
  EXPECT_EQ(1u, requests_received_on_server());

  // Inspect the URL from the proto.
  auto proto = GetLastCompanionProtoFromUrlLoad();
  ASSERT_TRUE(proto.has_value());
  EXPECT_TRUE(proto->page_url().empty());
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
                       SubsequentNavigationWithAndWithoutMsbb) {
  // Load a page on the active tab and open companion side panel.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(1u, requests_received_on_server());
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Turn off Msbb, and navigate to a URL. Verify that URL isn't sent.
  EnableMsbb(false);
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl2)));
  auto proto = GetLastCompanionProtoFromPostMessage();
  EXPECT_FALSE(proto.has_value());

  // Turn on Msbb, and navigate to a URL. Verify that URL is sent.
  EnableMsbb(true);
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl3)));
  proto = GetLastCompanionProtoFromPostMessage();
  ASSERT_TRUE(proto.has_value());
  EXPECT_EQ(proto->page_url(), CreateUrl(kHost, kRelativeUrl3));
}

class CompanionPageSameTabBrowserTest : public CompanionPageBrowserTest {
 public:
  std::string ShouldOpenLinkInCurrentTab() override { return "true"; }
};

IN_PROC_BROWSER_TEST_F(CompanionPageSameTabBrowserTest,
                       LinkClickOnCompanionPage) {
  EnableSignInMsbbExps(/*signed_in=*/true, /*msbb=*/true, /*exps=*/true);
  ukm::TestAutoSetUkmRecorder ukm_recorder;
  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion,
                                 SidePanelOpenTrigger::kComboboxSelected);

  WaitForCompanionToBeLoaded();
  WaitForHistogram("Companion.SidePanel.OpenTrigger");
  WaitForHistogram("SidePanel.Companion.ShowTriggered");
  EXPECT_EQ(1u, requests_received_on_server());
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  base::StatisticsRecorder::ForgetHistogramForTesting(
      "Companion.SidePanel.OpenTrigger");
  base::StatisticsRecorder::ForgetHistogramForTesting(
      "SidePanel.Companion.ShowTriggered");

  // Click a link on the companion page. It should open in the same tab and
  // refresh the companion.
  content::TestNavigationObserver nav_observer(web_contents());
  std::string script =
      "document.getElementById('some_link').click(); waitForMessage();";
  EvalJs(script);
  nav_observer.Wait();

  // Close side panel and verify that open and show triggers are not recorded in
  // UKM or histograms for the navigation.
  side_panel_coordinator()->Close();
  ExpectUkmCount(&ukm_recorder, 2u);
  ExpectUkmEntryAt(&ukm_recorder, 0,
                   ukm::builders::Companion_PageView::kOpenTriggerName,
                   static_cast<int>(SidePanelOpenTrigger::kComboboxSelected));
  ExpectUkmEntryAtIndexToNotHaveMetric(
      &ukm_recorder, 1, ukm::builders::Companion_PageView::kOpenTriggerName);
  EXPECT_FALSE(base::StatisticsRecorder::FindHistogram(
      "Companion.SidePanel.OpenTrigger"));
  EXPECT_FALSE(base::StatisticsRecorder::FindHistogram(
      "SidePanel.Companion.ShowTriggered"));
}

// This interaction tests that pages in the tab frame opened from the side panel
// are correctly marked as being non-skippable despite the tab frame not
// receiving a user gesture.
//   1. Have the side panel open A in the tab.
//   2. Have the side panel open B1 in the tab.
//   3. B1 automatically redirects to B2 to attempt to trap the user.
//   4. Navigating backwards from B2 should skip back to A.
//   5. Navigating backwards from A should skip back to the tab's initial page.
IN_PROC_BROWSER_TEST_F(CompanionPageSameTabBrowserTest,
                       LinkClickWithRedirectNavigatesBackProperly) {
  const GURL initial_url = CreateUrl(kHost, "/initial.html");
  const GURL a_url = CreateUrl(kHost, "/A.html");
  const GURL b1_url = CreateUrl(kHost, "/B1.html");
  const GURL b2_url = CreateUrl(kHost, "/B2.html");

  // Load the initial page on the active tab and open companion side panel
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion,
                                 SidePanelOpenTrigger::kComboboxSelected);

  // The history stack should currently have 2 entries, the page the browser
  // process starts with, and the initial page we navigate to.
  EXPECT_EQ(2, web_contents()->GetController().GetEntryCount());

  // Ensure companion is open and loaded.
  WaitForCompanionToBeLoaded();
  EXPECT_EQ(1u, requests_received_on_server());
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Have the side panel open page A in the main tab contents.
  ClickUrlInCompanion(a_url);
  EXPECT_EQ(3, web_contents()->GetController().GetEntryCount());

  // Have the side panel open page B1 in the main tab contents, then immediately
  // redirect to B2.
  ClickUrlInCompanionWithRedirect(b1_url, b2_url);

  // Ensure redirect actually happened. If redirected properly, the middle page
  // shouldn't be in the NavigationController, so we should still be at 4
  // entries. i.e. about:blank -> initial.html -> a.html -> b2.html
  EXPECT_EQ(b2_url, web_contents()->GetURL());
  EXPECT_EQ(4, web_contents()->GetController().GetEntryCount());

  // Go back from page B2. We should return to page A.
  PressBackButton();
  EXPECT_EQ(a_url, web_contents()->GetURL());

  // Go back from page A. We should return to the initial page.
  PressBackButton();
  EXPECT_EQ(initial_url, web_contents()->GetURL());
}

// This tests that only the final page in the tab frame arrived at from a
// redirection chain initiated from the side panel is marked as skippable and
// not the intermediate pages in the chain.
//   1. Have the side panel open A1 in the tab.
//   2. A1 automatically redirects to A2 to attempt to trap the user.
//   3. Have the side panel open B in the tab.
//   4. Navigating backwards from B should skip back to A2.
//   5. Navigating backwards from A2 should skip back to the tab's initial page.
IN_PROC_BROWSER_TEST_F(
    CompanionPageSameTabBrowserTest,
    LinkClickWithRedirectPlusSubsequentUserNavigationNavigatesBackProperly) {
  const GURL initial_url = CreateUrl(kHost, "/initial.html");
  const GURL a1_url = CreateUrl(kHost, "/A1.html");
  const GURL a2_url = CreateUrl(kHost, "/A2.html");
  const GURL b_url = CreateUrl(kHost, "/B.html");

  // Load the initial page on the active tab and open companion side panel
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion,
                                 SidePanelOpenTrigger::kComboboxSelected);

  // Ensure companion is open and loaded.
  WaitForCompanionToBeLoaded();
  EXPECT_EQ(1u, requests_received_on_server());
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);
  EXPECT_EQ(2, web_contents()->GetController().GetEntryCount());

  // Have the side panel open page A1 in the main tab contents, then immediately
  // redirect to A2.
  ClickUrlInCompanionWithRedirect(a1_url, a2_url);

  // Ensure redirect actually happened. If redirected properly, the middle page
  // shouldn't be in the NavigationController, so we should still be at 3
  // entries. i.e. about:blank -> initial.html -> a2.html
  EXPECT_EQ(a2_url, web_contents()->GetURL());
  EXPECT_EQ(3, web_contents()->GetController().GetEntryCount());

  // Have the side panel open page B in the main tab contents.
  ClickUrlInCompanion(b_url);

  // Ensure that all pages, including redirects, are in the navigation stack.
  // This ensures the test fails if a redirect doesn't occur, which would
  // indicate the test is not WAI.
  EXPECT_EQ(4, web_contents()->GetController().GetEntryCount());

  // Go back from page B. We should return to page A2.
  PressBackButton();
  EXPECT_EQ(a2_url, web_contents()->GetURL());

  // Go back from page A2. We should return to the initial page.
  PressBackButton();
  EXPECT_EQ(initial_url, web_contents()->GetURL());
}

IN_PROC_BROWSER_TEST_F(CompanionPageSameTabBrowserTest,
                       LinkClickInNestedIframeOpens) {
  const GURL initial_url = CreateUrl(kHost, "/initial.html");
  const GURL clicked_url = CreateUrl(kHost, "/clicked.html");

  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(1u, requests_received_on_server());
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);
  EXPECT_EQ(2, web_contents()->GetController().GetEntryCount());

  // Click a URL that appears in a nested iframe
  ClickUrlInCompanion(clicked_url,
                      /*wait_for_navigation=*/true,
                      /*wait_for_message=*/false,
                      /*click_in_nested_iframe=*/true);

  EXPECT_EQ(3, web_contents()->GetController().GetEntryCount());
}

IN_PROC_BROWSER_TEST_F(CompanionPageSameTabBrowserTest,
                       LinkClickFromUntrustedSourceDontOpen) {
  const GURL initial_url = CreateUrl(kHost, "/initial.html");
  const GURL clicked_url = CreateUrl(kHost, "/clicked.html");
  const GURL malicious_url =
      CreateUrl("www.malicious-site.com", "/companion_iframe.html");

  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), initial_url));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(1u, requests_received_on_server());
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);
  EXPECT_EQ(2, web_contents()->GetController().GetEntryCount());

  // Set Search Companion iframe to to an untrusted domain
  content::WebContents* companion_web_contents =
      GetCompanionWebContents(browser());
  std::string script =
      "const iframe = document.getElementsByTagName('iframe')[0];iframe.src='" +
      malicious_url.spec() + "';";
  EXPECT_TRUE(content::ExecJs(companion_web_contents, script));
  WaitForCompanionIframeReload();

  // Click a URL
  ClickUrlInCompanion(clicked_url, false);

  // Ensure side panel did not open link
  EXPECT_NE(clicked_url, companion_web_contents->GetURL());

  // Ensure main tab did not open link
  EXPECT_EQ(nullptr, web_contents()->GetController().GetPendingEntry());
  EXPECT_EQ(2, web_contents()->GetController().GetEntryCount());
  EXPECT_NE(clicked_url, web_contents()->GetURL());
}

IN_PROC_BROWSER_TEST_F(CompanionPageSameTabBrowserTest,
                       LinkClickOnCompanionPageNotifiesViaPostMessage) {
  const GURL clicked_url = CreateUrl(kHost, "/clicked.html");
  // EnableSignInMsbbExps(/*signed_in=*/true, /*msbb=*/true, /*exps=*/true);

  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  // Open companion companion via toolbar entry point.
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  ClickUrlInCompanion(clicked_url);

  // Ensure browser sent post message
  EXPECT_EQ(clicked_url, GetLastLinkOpenedUrlFromPostMessage());
  EXPECT_EQ(kExpectedClobberLinkMetadata,
            GetLastLinkOpenedMetadataFromPostMessage());
}

IN_PROC_BROWSER_TEST_F(CompanionPageSameTabBrowserTest,
                       LinkClickOnSearchURLNotifiesViaPostMessage) {
  const GURL clicked_url = GURL("https://www.google.com/search?q=query");

  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  // Open companion companion via toolbar entry point.
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  ClickUrlInCompanion(clicked_url, /*wait_for_navigation=*/false,
                      /*wait_for_message=*/true);

  // Ensure browser sent post message
  EXPECT_EQ(clicked_url, GetLastLinkOpenedUrlFromPostMessage());
  EXPECT_EQ(kExpectedSearchUrlLinkMetadata,
            GetLastLinkOpenedMetadataFromPostMessage());
}

IN_PROC_BROWSER_TEST_F(CompanionPageSameTabBrowserTest,
                       LinkClickOnImgresURLNotifiesViaPostMessage) {
  const GURL clicked_url = GURL("https://www.google.com/imgres?imgurl=query");

  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  // Open companion companion via toolbar entry point.
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  ClickUrlInCompanion(clicked_url, /*wait_for_navigation=*/false,
                      /*wait_for_message=*/true);

  // Ensure browser sent post message
  EXPECT_EQ(clicked_url, GetLastLinkOpenedUrlFromPostMessage());
  EXPECT_EQ(kExpectedClobberLinkMetadata,
            GetLastLinkOpenedMetadataFromPostMessage());
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest, LinkClickOnCompanionPage) {
  EnableSignInMsbbExps(/*signed_in=*/true, /*msbb=*/true, /*exps=*/true);
  ukm::TestAutoSetUkmRecorder ukm_recorder;
  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion,
                                 SidePanelOpenTrigger::kComboboxSelected);

  WaitForCompanionToBeLoaded();
  WaitForHistogram("Companion.SidePanel.OpenTrigger");
  WaitForHistogram("SidePanel.Companion.ShowTriggered");
  EXPECT_EQ(1u, requests_received_on_server());
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  base::StatisticsRecorder::ForgetHistogramForTesting(
      "Companion.SidePanel.OpenTrigger");
  base::StatisticsRecorder::ForgetHistogramForTesting(
      "SidePanel.Companion.ShowTriggered");

  // Click a link. It should open in a new tab and open the companion side
  // panel. Wait for that event.
  EXPECT_TRUE(ExecJs("document.getElementById('some_link').click();"));
  WaitForHistogram("Companion.SidePanel.OpenTrigger");
  WaitForHistogram("SidePanel.Companion.ShowTriggered");
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());

  // Close side panel and verify UKM. There should be only one entry since the
  // side panel in the previous tab wasn't closed.
  side_panel_coordinator()->Close();
  ExpectUkmCount(&ukm_recorder, 1u);
  ExpectUkmEntryAt(
      &ukm_recorder, 0, ukm::builders::Companion_PageView::kOpenTriggerName,
      static_cast<int>(SidePanelOpenTrigger::kOpenedInNewTabFromSidePanel));

  // Switch to previous tab, close side panel, and verify UKM for previous tab.
  browser()->tab_strip_model()->ActivateTabAt(0);
  side_panel_coordinator()->Close();
  ExpectUkmCount(&ukm_recorder, 2u);
  ExpectUkmEntryAt(&ukm_recorder, 1,
                   ukm::builders::Companion_PageView::kOpenTriggerName,
                   static_cast<int>(SidePanelOpenTrigger::kComboboxSelected));
}

// TODO(crbug.com/40937688): Test is flaking on Linux MSAN bot
#if BUILDFLAG(IS_LINUX) && defined(MEMORY_SANITIZER)
#define MAYBE_LinkClickOnCompanionPageNotifiesNewTabSidePanelViaPostMessage \
  DISABLED_LinkClickOnCompanionPageNotifiesNewTabSidePanelViaPostMessage
#else
#define MAYBE_LinkClickOnCompanionPageNotifiesNewTabSidePanelViaPostMessage \
  LinkClickOnCompanionPageNotifiesNewTabSidePanelViaPostMessage
#endif
IN_PROC_BROWSER_TEST_F(
    CompanionPageBrowserTest,
    MAYBE_LinkClickOnCompanionPageNotifiesNewTabSidePanelViaPostMessage) {
  const GURL clicked_url = CreateUrl(kHost, "/clicked.html");
  // EnableSignInMsbbExps(/*signed_in=*/true, /*msbb=*/true, /*exps=*/true);

  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  // Open companion companion via toolbar entry point.
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  ClickUrlInCompanion(clicked_url);

  // Ensure browser sent post message
  EXPECT_EQ(clicked_url, GetLastLinkOpenedUrlFromPostMessage());
  EXPECT_EQ(kExpectedNewTabLinkMetadata,
            GetLastLinkOpenedMetadataFromPostMessage());
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
                       PageContentNotifiesViaPostMessage) {
  EnablePco(true);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), GURL("data:text/html;charset=utf-8,"
                      "<head><title>Title of the page</title></head>"
                      "<body>Content of the page</body>")));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  CompanionScriptBuilder builder(MethodType::kCompanionLoadingState);
  builder.loading_state = LoadingState::kStartedLoading;
  builder.wait_for_message = true;
  EXPECT_TRUE(ExecJs(builder.Build()));

  // Ensure browser sent post message
  EXPECT_EQ("Title of the page", GetLastPageTitleFromPostMessage());
  EXPECT_EQ("<body>Content of the page</body>",
            GetLastInnerHtmlFromPostMessage());
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
                       OpenCompanionPageWithVqsEnabled) {
  base::HistogramTester histogram_tester;
  OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile())
      ->OverrideTargetModelForTesting(
          optimization_guide::proto::
              OPTIMIZATION_TARGET_VISUAL_SEARCH_CLASSIFICATION,
          optimization_guide::TestModelInfoBuilder()
              .SetModelFilePath(model_file_path())
              .SetModelMetadata(model_metadata())
              .Build());

  EnableSignInMsbbExps(/*signed_in=*/true, /*msbb=*/true, /*exps=*/true);

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), vqs_url_server_.GetURL(kHost, kRelativeVisualQueryUrl)));

  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  CompanionScriptBuilder builder(MethodType::kCompanionLoadingState);
  builder.loading_state = LoadingState::kStartedLoading;
  EXPECT_TRUE(ExecJs(builder.Build()));

  // TODO(b/289113873) - Fix model flakiness for all platforms.
  // Reading models is flaky on certain platform, using this temporary path
  // check as a proxy; however, this should be done in a better way long-term.
  base::ScopedAllowBlockingForTesting allow_blocking;
  base::File model_file(model_file_path(),
                        base::File::FLAG_OPEN | base::File::FLAG_READ);
  if (base::PathExists(model_file_path()) && model_file.IsValid()) {
    WaitForHistogram("Companion.VisualQuery.SendVisualResultSuccess");
    histogram_tester.ExpectBucketCount(
        "Companion.VisualQuery.ClassifierModelAvailable", true, 1);
    histogram_tester.ExpectBucketCount(
        "Companion.VisualQuery.ClassificationResultsSize", 1, 1);
    histogram_tester.ExpectBucketCount(
        "Companion.VisualQuery.EndClassificationSuccess", true, 1);
    histogram_tester.ExpectBucketCount(
        "Companion.VisualQuery.SendVisualResultSuccess", true, 1);
  }

  CompanionScriptBuilder builder2(MethodType::kCompanionLoadingState);
  builder2.loading_state = LoadingState::kFinishedLoading;
  EXPECT_TRUE(ExecJs(builder2.Build()));

  // Verifies that we don't trigger the false state because we successfully
  // processed the image and sent result before receiving |kFinishedLoading|.
  histogram_tester.ExpectBucketCount(
      "Companion.VisualQuery.SendVisualResultSuccess", false, 0);

  side_panel_coordinator()->Close();
  // TODO(b/289113873) - Update iFrame to show UI and verify image bytes.
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest, AutoRefreshOnMsbb) {
  EnableSignInMsbbExps(/*signed_in=*/true, /*msbb=*/false, /*exps=*/false);

  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Inspect the URL from the proto. This will reset the proto.
  auto proto = GetLastCompanionProtoFromUrlLoad();
  ASSERT_TRUE(proto.has_value());
  EXPECT_TRUE(proto->page_url().empty());

  // Turn on Msbb via promo. This should auto refresh the companion page.
  CompanionScriptBuilder builder(MethodType::kOnPromoAction);
  builder.promo_type = PromoType::kMsbb;
  builder.promo_action = PromoAction::kAccepted;
  EXPECT_TRUE(ExecJs(builder.Build()));
  WaitForHistogram("Companion.PromoEvent");

  WaitForCompanionIframeReload();
  proto = GetLastCompanionProtoFromUrlLoad();
  ASSERT_TRUE(proto.has_value());
  EXPECT_EQ(proto->page_url(), CreateUrl(kHost, kRelativeUrl1));
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
                       AutoRefreshOnSigninStateChange) {
  EnableSignInMsbbExps(/*signed_in=*/false, /*msbb=*/false, /*exps=*/false);

  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);
  auto* companion_web_contents = GetCompanionWebContents(browser());

  // Inspect the URL from the proto. This will reset the proto.
  auto proto = GetLastCompanionProtoFromUrlLoad();
  ASSERT_TRUE(proto.has_value());
  EXPECT_TRUE(proto->page_url().empty());

  // Navigate to a new tab.
  chrome::NewTab(browser());

  // Sign-in to chrome. The companion should refresh automatically even though
  // it's in background.
  content::TestNavigationObserver nav_observer(companion_web_contents, 1);
  EnableSignInMsbbExps(/*signed_in=*/true, /*msbb=*/false, /*exps=*/false);

  nav_observer.Wait();
  proto = GetLastCompanionProtoFromUrlLoad();
  ASSERT_TRUE(proto.has_value());
  EXPECT_TRUE(proto->page_url().empty());
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
                       SamePageNavigationsAreSkipped) {
  EnableSignInMsbbExps(/*signed_in=*/true, /*msbb=*/true, /*exps=*/true);

  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl3)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  WaitForCompanionToBeLoaded();

  // Navigation to a same document URL. Verify that companion is not refreshed.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl4)));
  auto proto = GetLastCompanionProtoFromPostMessage();
  EXPECT_FALSE(proto.has_value());
}

// TODO(crbug.com/40930057): Flaky on linux-chromeos-chrome.
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_ReloadWillRefreshCompanion DISABLED_ReloadWillRefreshCompanion
#else
#define MAYBE_ReloadWillRefreshCompanion ReloadWillRefreshCompanion
#endif
IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
                       MAYBE_ReloadWillRefreshCompanion) {
  EnableSignInMsbbExps(/*signed_in=*/true, /*msbb=*/true, /*exps=*/true);

  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  WaitForCompanionToBeLoaded();
  auto proto = GetLastCompanionProtoFromUrlLoad();
  ASSERT_TRUE(proto.has_value());
  EXPECT_EQ(proto->page_url(), CreateUrl(kHost, kRelativeUrl1));

  // Post message so that shown events are flushed.
  CompanionScriptBuilder builder(MethodType::kRecordUiSurfaceShown);
  builder.ui_surface = UiSurface::kCQ;
  builder.ui_surface_position = 3;
  builder.child_element_available_count = 8;
  builder.child_element_shown_count = 5;
  EXPECT_TRUE(ExecJs(builder.Build()));
  WaitForHistogram("Companion.CQ.Shown");

  // Reload the page. It should refresh the companion via postmessage.
  content::TestNavigationObserver nav_observer(web_contents(), 1);
  chrome::Reload(browser(), WindowOpenDisposition::CURRENT_TAB);
  nav_observer.Wait();

  proto = GetLastCompanionProtoFromPostMessage();
  ASSERT_TRUE(proto.has_value());
  EXPECT_EQ(proto->page_url(), CreateUrl(kHost, kRelativeUrl1));

  CompanionScriptBuilder builder2(MethodType::kRecordUiSurfaceShown);
  builder2.ui_surface = UiSurface::kRelQr;
  builder2.ui_surface_position = 3;
  builder2.child_element_available_count = 8;
  builder2.child_element_shown_count = 5;
  EXPECT_TRUE(ExecJs(builder2.Build()));
  WaitForHistogram("Companion.RelQr.Shown");
  histogram_tester_->ExpectTotalCount("Companion.FullLoad.Latency", 1);
  histogram_tester_->ExpectTotalCount("Companion.NavigationLoad.Latency", 1);
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
                       UiSurfaceShownAndClickedForListSurfaces) {
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  // Open companion companion via toolbar entry point.
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Post message for showing CQ surface. Verify histograms.
  CompanionScriptBuilder builder(MethodType::kRecordUiSurfaceShown);
  builder.ui_surface = UiSurface::kCQ;
  builder.ui_surface_position = 3;
  builder.child_element_available_count = 8;
  builder.child_element_shown_count = 5;
  EXPECT_TRUE(ExecJs(builder.Build()));

  WaitForHistogram("Companion.CQ.Shown");
  histogram_tester_->ExpectBucketCount("Companion.CQ.Shown",
                                       /*sample=*/true, /*expected_count=*/1);
  histogram_tester_->ExpectTotalCount("Companion.FullLoad.Latency", 1);
  histogram_tester_->ExpectTotalCount("Companion.NavigationLoad.Latency", 0);

  // Post message for click metrics. Verify histograms.
  CompanionScriptBuilder builder2(MethodType::kRecordUiSurfaceClicked);
  builder2.ui_surface = UiSurface::kCQ;
  builder2.click_position = 3;
  EXPECT_TRUE(ExecJs(builder2.Build()));
  WaitForHistogram("Companion.CQ.Clicked");
  histogram_tester_->ExpectBucketCount("Companion.CQ.Clicked",
                                       /*sample=*/true,
                                       /*expected_count=*/1);
  histogram_tester_->ExpectBucketCount("Companion.CQ.ClickPosition",
                                       /*sample=*/3,
                                       /*expected_count=*/1);

  // Close side panel and verify UKM.
  side_panel_coordinator()->Close();
  ExpectUkmEntry(&ukm_recorder,
                 ukm::builders::Companion_PageView::kCQ_LastEventName,
                 static_cast<int>(companion::UiEvent::kClicked));
  ExpectUkmEntry(&ukm_recorder,
                 ukm::builders::Companion_PageView::kCQ_ClickPositionName, 3);

  ExpectUkmEntry(&ukm_recorder,
                 ukm::builders::Companion_PageView::kCQ_ComponentPositionName,
                 3);
  ExpectUkmEntry(&ukm_recorder,
                 ukm::builders::Companion_PageView::kCQ_NumEntriesAvailableName,
                 8);
  ExpectUkmEntry(&ukm_recorder,
                 ukm::builders::Companion_PageView::kCQ_NumEntriesShownName, 5);
  ExpectUkmEntry(&ukm_recorder,
                 ukm::builders::Companion_PageView::kCQ_ClickPositionName, 3);
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
                       UiSurfaceShownAndClickedForNonListSurfaces) {
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  // Open companion companion via toolbar entry point.
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Post message for showing PH surface. Verify histograms.
  CompanionScriptBuilder builder(MethodType::kRecordUiSurfaceShown);
  builder.ui_surface = UiSurface::kPH;
  builder.ui_surface_position = 3;
  EXPECT_TRUE(ExecJs(builder.Build()));

  WaitForHistogram("Companion.PH.Shown");
  histogram_tester_->ExpectBucketCount("Companion.PH.Shown",
                                       /*sample=*/true, /*expected_count=*/1);
  histogram_tester_->ExpectTotalCount("Companion.FullLoad.Latency", 1);
  histogram_tester_->ExpectTotalCount("Companion.NavigationLoad.Latency", 0);

  // Post message for click metrics. Verify histograms.
  CompanionScriptBuilder builder2(MethodType::kRecordUiSurfaceClicked);
  builder2.ui_surface = UiSurface::kPH;
  EXPECT_TRUE(ExecJs(builder2.Build()));
  WaitForHistogram("Companion.PH.Clicked");
  histogram_tester_->ExpectBucketCount("Companion.PH.Clicked",
                                       /*sample=*/true,
                                       /*expected_count=*/1);
  histogram_tester_->ExpectTotalCount("Companion.PH.ClickPosition", 0);

  // Close side panel and verify UKM.
  side_panel_coordinator()->Close();
  ExpectUkmEntry(&ukm_recorder,
                 ukm::builders::Companion_PageView::kPH_LastEventName,
                 static_cast<int>(companion::UiEvent::kClicked));

  ExpectUkmEntry(&ukm_recorder,
                 ukm::builders::Companion_PageView::kPH_ComponentPositionName,
                 3);
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest, PostMessageForPromoEvents) {
  ukm::TestAutoSetUkmRecorder ukm_recorder;
  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  // Open companion companion via toolbar entry point.
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Show a promo, user rejects it. Verify histogram.
  CompanionScriptBuilder builder(MethodType::kOnPromoAction);
  builder.promo_type = PromoType::kMsbb;
  builder.promo_action = PromoAction::kRejected;
  EXPECT_TRUE(ExecJs(builder.Build()));

  WaitForHistogram("Companion.PromoEvent");
  histogram_tester_->ExpectBucketCount("Companion.PromoEvent",
                                       companion::PromoEvent::kMsbbRejected,
                                       /*expected_count=*/1);
  // Close side panel and verify UKM.
  side_panel_coordinator()->Close();
  ExpectUkmEntry(&ukm_recorder,
                 ukm::builders::Companion_PageView::kPromoEventName,
                 static_cast<int>(companion::PromoEvent::kMsbbRejected));
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest, OpenUrlInBrowser) {
  ukm::TestAutoSetUkmRecorder ukm_recorder;
  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  EXPECT_EQ(1, browser()->tab_strip_model()->count());

  // Open companion companion via toolbar entry point.
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Show exps promo, user accepts it.
  CompanionScriptBuilder builder(MethodType::kOpenUrlInBrowser);
  builder.url_to_open = kExpectedExpsPromoUrl;
  builder.use_new_tab = true;
  EXPECT_TRUE(ExecJs(builder.Build()));

  // Verify that a new tab opens up to load the exps URL.
  WaitForTabCount(2);
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());

  EXPECT_TRUE(web_contents()->GetVisibleURL().spec().starts_with(
      kExpectedExpsPromoUrl));
}

#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_CHROMEOS_LACROS)
IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest, SigninLoadsInNewTab) {
  ukm::TestAutoSetUkmRecorder ukm_recorder;
  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  EXPECT_EQ(1, browser()->tab_strip_model()->count());

  // Open companion companion via toolbar entry point.
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Show sign-in promo, user accepts it.
  CompanionScriptBuilder builder(MethodType::kOnPromoAction);
  builder.promo_type = PromoType::kSignin;
  builder.promo_action = PromoAction::kAccepted;
  EXPECT_TRUE(ExecJs(builder.Build()));

  // Verify that a new tab opens up to load the sign-in URL.
  WaitForTabCount(2);
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
  EXPECT_TRUE(web_contents()->GetVisibleURL().spec().starts_with(
      "https://accounts.google.com/signin/chrome/sync"));
}
#endif

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest, RegionSearch) {
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  // Open companion companion via toolbar entry point.
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Start region search. Verify histograms.
  CompanionScriptBuilder builder(MethodType::kOnRegionSearchClicked);
  EXPECT_TRUE(ExecJs(builder.Build()));

  CompanionScriptBuilder builder2(MethodType::kRecordUiSurfaceClicked);
  builder2.ui_surface = UiSurface::kRegionSearch;
  EXPECT_TRUE(ExecJs(builder2.Build()));
  WaitForHistogram("Companion.RegionSearch.Clicked");
  histogram_tester_->ExpectBucketCount("Companion.RegionSearch.Clicked",
                                       /*sample=*/true,
                                       /*expected_count=*/1);

  side_panel_coordinator()->Close();
  ExpectUkmEntry(
      &ukm_recorder,
      ukm::builders::Companion_PageView::kRegionSearch_ClickCountName, 1);
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest, OnExpsOptInStatusAvailable) {
  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  // Open companion companion via toolbar entry point.
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Send exps optin status. Verify histograms.
  CompanionScriptBuilder builder(MethodType::kOnExpsOptInStatusAvailable);
  builder.is_exps_opted_in = true;
  EXPECT_TRUE(ExecJs(builder.Build()));
  WaitForHistogram("Companion.IsUserOptedInToExps");
  histogram_tester_->ExpectBucketCount("Companion.IsUserOptedInToExps",
                                       /*sample=*/true,
                                       /*expected_count=*/1);

  // Verify that the optin status is saved to a pref.
  EXPECT_TRUE(browser()->profile()->GetPrefs()->GetBoolean(
      companion::kExpsOptInStatusGrantedPref));
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest, OpenInNewTabButtonClicked) {
  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  // Open companion companion via toolbar entry point.
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Send open in new tab URL.
  auto open_in_new_tab_url = CreateUrl(kHost, kRelativeUrl2);
  CompanionScriptBuilder builder(MethodType::kOnOpenInNewTabButtonURLChanged);
  builder.url_for_open_in_new_tab = open_in_new_tab_url.spec();
  EXPECT_TRUE(ExecJs(builder.Build()));

  // Send another message so that we can wait for the histogram.
  CompanionScriptBuilder builder2(MethodType::kOnExpsOptInStatusAvailable);
  builder2.is_exps_opted_in = true;
  EXPECT_TRUE(ExecJs(builder2.Build()));
  WaitForHistogram("Companion.IsUserOptedInToExps");

  EXPECT_EQ(side_panel_coordinator()
                ->GetCurrentSidePanelEntryForTesting()
                ->GetOpenInNewTabURL(),
            open_in_new_tab_url);
  side_panel_coordinator()->OpenInNewTab();
  WaitForTabCount(2);
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
  EXPECT_TRUE(web_contents()->GetVisibleURL().spec().starts_with(
      open_in_new_tab_url.spec()));

  // Close side panel and reopen. The new tab button shouldn't be shown.
  side_panel_coordinator()->Close();
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()
                ->GetCurrentSidePanelEntryForTesting()
                ->GetOpenInNewTabURL(),
            GURL());
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest, PhFeedbackWithReportContent) {
  ukm::TestAutoSetUkmRecorder ukm_recorder;
  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  EXPECT_EQ(1, browser()->tab_strip_model()->count());

  // Open companion companion via toolbar entry point.
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Show exps promo, user accepts it.
  CompanionScriptBuilder builder(MethodType::kOnPhFeedback);
  builder.ph_feedback = PhFeedback::kReportContent;
  EXPECT_TRUE(ExecJs(builder.Build()));

  CompanionScriptBuilder builder2(MethodType::kOpenUrlInBrowser);
  builder2.url_to_open = kPhReportingUrl;
  builder2.use_new_tab = true;
  EXPECT_TRUE(ExecJs(builder2.Build()));

  // Verify that a new tab opens up to load the exps URL.
  WaitForTabCount(2);
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
  EXPECT_TRUE(
      web_contents()->GetVisibleURL().spec().starts_with(kPhReportingUrl));
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
                       PostMessageForCqCandidatesAvailable) {
  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  // Open companion companion via toolbar entry point.
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  CompanionScriptBuilder builder(MethodType::kOnCqCandidatesAvailable);
  builder.ui_surface = UiSurface::kCQ;
  builder.cq_text_directives = std::vector<std::string>{"abc", "def"};
  builder.wait_for_message = true;
  content::EvalJsResult eval_js_result = EvalJs(builder.Build());
  const base::Value promise_values = eval_js_result.ExtractList();
  EXPECT_EQ(2u, promise_values.GetList().size());
  EXPECT_EQ(content::ListValueOf(false, false), promise_values);
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
                       PostMessageForCqJumptagClicked) {
  // Load a page on the active tab.
  GURL url = page_url_server_.GetURL(kHost, kRelativeUrl1);
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  // Open companion companion via toolbar entry point.
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Click a cq jumptag.
  CompanionScriptBuilder builder(MethodType::kOnCqJumptagClicked);
  builder.ui_surface = UiSurface::kCQ;
  builder.text_directive = "English";
  EXPECT_TRUE(ExecJs(builder.Build()));
  WaitForHistogram("Companion.CQ.TextHighlight.Success");
  // TODO(b/280453152): Fix the metrics expectation.
  histogram_tester_->ExpectBucketCount("Companion.CQ.TextHighlight.Success",
                                       /*sample=*/false, /*expected_count=*/1);
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
                       OpenedFromContextMenuTextSearch) {
  ukm::TestAutoSetUkmRecorder ukm_recorder;
  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));

  // Start a text query via context menu. It should open companion side panel.
  auto* companion_helper =
      companion::CompanionTabHelper::FromWebContents(web_contents());
  companion_helper->ShowCompanionSidePanelForSearchURL(GURL(kSearchQueryUrl));
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Close side panel and verify UKM.
  side_panel_coordinator()->Close();
  ExpectUkmEntry(
      &ukm_recorder, ukm::builders::Companion_PageView::kOpenTriggerName,
      static_cast<int>(SidePanelOpenTrigger::kContextMenuSearchOption));
  histogram_tester_->ExpectBucketCount("Companion.SidePanel.ShowUiSuccess",
                                       true, 1);
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
                       OpenedFromContextMenuImageSearch) {
  ukm::TestAutoSetUkmRecorder ukm_recorder;
  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));

  // Start a image query via context menu. It should open companion side panel.
  GURL src_url = CreateUrl(kHost, kRelativeUrl2);
  gfx::Size original_size(8, 8);
  gfx::Size downscaled_size(8, 8);
  std::vector<uint8_t> thumbnail_data(64, 0);
  std::string content_type("image/jpeg");

  std::string source_lang = "";
  std::string target_lang = "en";
  ChromeTranslateClient* chrome_translate_client =
      ChromeTranslateClient::FromWebContents(web_contents());
  chrome_translate_client->GetTranslateManager()
      ->GetLanguageState()
      ->SetSourceLanguage(source_lang);
  chrome_translate_client->GetTranslateManager()
      ->GetLanguageState()
      ->SetCurrentLanguage(target_lang);
  auto* companion_helper =
      companion::CompanionTabHelper::FromWebContents(web_contents());
  companion_helper->ShowCompanionSidePanelForImage(
      src_url,
      /*is_image_translate=*/false,
      /*additional_query_params_modified=*/"", thumbnail_data, original_size,
      downscaled_size, content_type);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Close side panel and verify UKM.
  side_panel_coordinator()->Close();
  ExpectUkmEntry(&ukm_recorder,
                 ukm::builders::Companion_PageView::kOpenTriggerName,
                 static_cast<int>(SidePanelOpenTrigger::kLensContextMenu));
  // The language params should be unset when is_image_translate=false.
  EXPECT_EQ(GetLastSourceLang(), "");
  EXPECT_EQ(GetLastTargetLang(), "");
  // The viewport dimension params should be set to a value
  EXPECT_TRUE(GetLastViewportHeightParam() > 0);
  EXPECT_TRUE(GetLastViewportWidthParam() > 0);
  histogram_tester_->ExpectBucketCount(
      "Search.Lens.ViewportDimensionsSent.Success", true, 1);
  histogram_tester_->ExpectBucketCount("Companion.SidePanel.ShowUiSuccess",
                                       true, 1);
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
                       OpenedFromContextMenuImageSearchWithTranslate) {
  ukm::TestAutoSetUkmRecorder ukm_recorder;
  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));

  // Start a image query via context menu. It should open companion side panel.
  GURL src_url = CreateUrl(kHost, kRelativeUrl2);
  gfx::Size original_size(8, 8);
  gfx::Size downscaled_size(8, 8);
  std::vector<uint8_t> thumbnail_data(64, 0);
  std::string content_type("image/jpeg");

  std::string source_lang = "";
  std::string target_lang = "en";
  ChromeTranslateClient* chrome_translate_client =
      ChromeTranslateClient::FromWebContents(web_contents());
  chrome_translate_client->GetTranslateManager()
      ->GetLanguageState()
      ->SetSourceLanguage(source_lang);
  chrome_translate_client->GetTranslateManager()
      ->GetLanguageState()
      ->SetCurrentLanguage(target_lang);
  auto* companion_helper =
      companion::CompanionTabHelper::FromWebContents(web_contents());
  companion_helper->ShowCompanionSidePanelForImage(
      src_url,
      /*is_image_translate=*/true,
      /*additional_query_params_modified=*/"", thumbnail_data, original_size,
      downscaled_size, content_type);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Close side panel and verify UKM.
  side_panel_coordinator()->Close();
  ExpectUkmEntry(&ukm_recorder,
                 ukm::builders::Companion_PageView::kOpenTriggerName,
                 static_cast<int>(SidePanelOpenTrigger::kLensContextMenu));
  EXPECT_EQ(GetLastSourceLang(), source_lang);
  EXPECT_EQ(GetLastTargetLang(), target_lang);
  histogram_tester_->ExpectBucketCount("Companion.SidePanel.ShowUiSuccess",
                                       true, 1);
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest, OpenedFromEntryPoint) {
  ukm::TestAutoSetUkmRecorder ukm_recorder;

  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  // Open companion from entry point via dropdown.
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion,
                                 SidePanelOpenTrigger::kComboboxSelected);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Close side panel and verify UKM.
  side_panel_coordinator()->Close();
  ExpectUkmEntry(&ukm_recorder,
                 ukm::builders::Companion_PageView::kOpenTriggerName,
                 static_cast<int>(SidePanelOpenTrigger::kComboboxSelected));
  histogram_tester_->ExpectBucketCount("Companion.SidePanel.ShowUiSuccess",
                                       true, 1);
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
                       SubsequentContextMenuTextSearch) {
  ukm::TestAutoSetUkmRecorder ukm_recorder;
  // Load a page on the active tab.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  ASSERT_EQ(side_panel_coordinator()->GetCurrentEntryId(), std::nullopt);

  // Open companion from pinned entry point.
  side_panel_coordinator()->Show(
      SidePanelEntry::Id::kSearchCompanion,
      SidePanelOpenTrigger::kPinnedEntryToolbarButton);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Start a text query via context menu.
  auto* companion_helper =
      companion::CompanionTabHelper::FromWebContents(web_contents());
  companion_helper->ShowCompanionSidePanelForSearchURL(GURL(kSearchQueryUrl));
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing());

  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Close side panel and verify UKM.
  side_panel_coordinator()->Close();
  ExpectUkmEntry(
      &ukm_recorder, ukm::builders::Companion_PageView::kOpenTriggerName,
      static_cast<int>(SidePanelOpenTrigger::kPinnedEntryToolbarButton));
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
                       RefreshCompanionPageMessageDoesReload) {
  EnableSignInMsbbExps(/*signed_in=*/true, /*msbb=*/true, /*exps=*/true);

  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  WaitForCompanionToBeLoaded();
  auto proto = GetLastCompanionProtoFromUrlLoad();
  ASSERT_TRUE(proto.has_value());
  EXPECT_EQ(proto->page_url(), CreateUrl(kHost, kRelativeUrl1));

  // Simulate a message to refresh companion page.
  CompanionScriptBuilder builder(MethodType::kRefreshCompanionPage);
  EXPECT_TRUE(ExecJs(builder.Build()));

  WaitForCompanionIframeReload();
  proto = GetLastCompanionProtoFromUrlLoad();
  ASSERT_TRUE(proto.has_value());
  EXPECT_EQ(proto->page_url(), CreateUrl(kHost, kRelativeUrl1));
}

IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
                       ServerSideUrlFilterEventIsRecorded) {
  EnableSignInMsbbExps(/*signed_in=*/true, /*msbb=*/true, /*exps=*/true);

  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  WaitForCompanionToBeLoaded();
  auto proto = GetLastCompanionProtoFromUrlLoad();
  ASSERT_TRUE(proto.has_value());
  EXPECT_EQ(proto->page_url(), CreateUrl(kHost, kRelativeUrl1));

  // Simulate a message that a sensitive URL was filtered out.
  CompanionScriptBuilder builder(MethodType::kServerSideUrlFilterEvent);
  EXPECT_TRUE(ExecJs(builder.Build()));

  WaitForHistogram("Companion.ServerSideUrlFilterEvent");
  histogram_tester_->ExpectBucketCount("Companion.ServerSideUrlFilterEvent",
                                       true, 1);
}

// This test verifies that a new tab that was opened from a page with Search
// Companion open, also opens Search Companion in the new tab.
IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest, NewTabFromMainPageOpensCsc) {
  EnableSignInMsbbExps(/*signed_in=*/true, /*msbb=*/true, /*exps=*/true);
  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion,
                                 SidePanelOpenTrigger::kComboboxSelected);

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(1u, requests_received_on_server());
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  ClickNewTabUrlInMainPage(CreateUrl(kHost, kRelativeUrl2));

  WaitForTabCount(2);
  EXPECT_TRUE(side_panel_coordinator()->IsSidePanelEntryShowing(
      SidePanelEntry::Key(SidePanelEntry::Id::kSearchCompanion)));
}

// This test verifies that a new tab that was opened from a page with side panel
// open but not on Search Companion, does not open Search Companion in the new
// tab.
IN_PROC_BROWSER_TEST_F(
    CompanionPageBrowserTest,
    NewTabFromMainPageWhileCompanionHiddenDoesNotOpenCompanion) {
  EnableSignInMsbbExps(/*signed_in=*/true, /*msbb=*/true, /*exps=*/true);
  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion,
                                 SidePanelOpenTrigger::kComboboxSelected);

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(1u, requests_received_on_server());
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  // Hide Search Companion
  side_panel_coordinator()->Show(SidePanelEntry::Id::kReadingList);
  WaitForSidePanelEntryShowing(SidePanelEntry::Id::kReadingList);

  ClickNewTabUrlInMainPage(CreateUrl(kHost, kRelativeUrl2));

  WaitForTabCount(2);
  EXPECT_FALSE(side_panel_coordinator()->IsSidePanelEntryShowing(
      SidePanelEntry::Key(SidePanelEntry::Id::kSearchCompanion)));
}

// This test verifies that a new tab that was opened from a page with side panel
// closed, does not open Search Companion in the new tab.
IN_PROC_BROWSER_TEST_F(
    CompanionPageBrowserTest,
    NewTabFromMainPageWhileSidePanelClosedDoesNotOpenCompanion) {
  EnableSignInMsbbExps(/*signed_in=*/true, /*msbb=*/true, /*exps=*/true);
  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion,
                                 SidePanelOpenTrigger::kComboboxSelected);

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(1u, requests_received_on_server());
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);

  side_panel_coordinator()->Close();
  ClickNewTabUrlInMainPage(CreateUrl(kHost, kRelativeUrl2));

  WaitForTabCount(2);
  EXPECT_FALSE(side_panel_coordinator()->IsSidePanelEntryShowing(
      SidePanelEntry::Key(SidePanelEntry::Id::kSearchCompanion)));
}

class CompanionPageDisabledBrowserTest : public CompanionPageBrowserTest {
 public:
  CompanionPageDisabledBrowserTest() : CompanionPageBrowserTest() {
    enable_feature_side_panel_companion_ = false;
  }
};

// Verifies the behavior when companion feature is disabled but a navigation to
// exps registration URL is observed.
IN_PROC_BROWSER_TEST_F(CompanionPageDisabledBrowserTest,
                       PRE_ObservesExpsRegistrationSuccessURL) {
  EXPECT_TRUE(companion::IsCompanionFeatureEnabled());
  EXPECT_TRUE(base::FeatureList::IsEnabled(
      companion::features::internal::
          kCompanionEnabledByObservingExpsNavigations));
  EXPECT_FALSE(base::FeatureList::IsEnabled(
      companion::features::internal::kSidePanelCompanion));

  // Navigate to a random page.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
      companion::kHasNavigatedToExpsSuccessPage));

  // Load a page on the active tab and open companion side panel.
  // Verify that companion is not enabled even though
  // `kCompanionEnabledByObservingExpsNavigations` is enabled.
  EXPECT_TRUE(companion::IsCompanionFeatureEnabled());
  WaitForMainPageToBeLoaded(kRelativeUrl1);
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_FALSE(side_panel_coordinator()->GetCurrentEntryId().has_value());
  EXPECT_EQ(0u, requests_received_on_server());
  EXPECT_FALSE(PinnedToolbarActionsModel::Get(browser()->profile())
                   ->Contains(kActionSidePanelShowSearchCompanion));

  base::HistogramTester histogram_tester;

  // Navigate to exps registration success page. It should enable the pref and
  // companion.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
                                           GURL(kExpsRegistrationSuccessUrl)));
  // Verify that the pref and companion are enabled now.
  EXPECT_TRUE(browser()->profile()->GetPrefs()->GetBoolean(
      companion::kHasNavigatedToExpsSuccessPage));
  histogram_tester.ExpectTotalCount(
      "Companion.HasNavigatedToExpsSuccessPagePref.OnChanged", 1);
  histogram_tester.ExpectBucketCount(
      "Companion.HasNavigatedToExpsSuccessPagePref.OnChanged", 1, 1);
  histogram_tester.ExpectTotalCount("Companion.SidePanelAvailabilityChanged",
                                    1);
  histogram_tester.ExpectBucketCount("Companion.SidePanelAvailabilityChanged",
                                     1 /* kUnavailableToAvailable */, 1);

  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);
  EXPECT_EQ(1u, requests_received_on_server());
  // Companion is immediately pinned.
  EXPECT_TRUE(PinnedToolbarActionsModel::Get(browser()->profile())
                  ->Contains(kActionSidePanelShowSearchCompanion));
}

// Verifies the behavior when companion feature is disabled but a navigation to
// exps registration URL is observed. Restart the browser and verify that
// companion is active and pinned.
//
// TODO(crbug.com/334977785): Flaky.
IN_PROC_BROWSER_TEST_F(CompanionPageDisabledBrowserTest,
                       DISABLED_ObservesExpsRegistrationSuccessURL) {
  EXPECT_TRUE(companion::IsCompanionFeatureEnabled());
  EXPECT_TRUE(base::FeatureList::IsEnabled(
      companion::features::internal::
          kCompanionEnabledByObservingExpsNavigations));
  EXPECT_FALSE(base::FeatureList::IsEnabled(
      companion::features::internal::kSidePanelCompanion));

  // Verify that the pref and companion are enabled.
  EXPECT_TRUE(browser()->profile()->GetPrefs()->GetBoolean(
      companion::kHasNavigatedToExpsSuccessPage));

  // Load a page on the active tab and open companion side panel.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);
  EXPECT_EQ(1u, requests_received_on_server());

  // Companion should be pinned now.
  EXPECT_TRUE(PinnedToolbarActionsModel::Get(browser()->profile())
                  ->Contains(kActionSidePanelShowSearchCompanion));
}

class CompanionPagePolicyBrowserTest : public CompanionPageBrowserTest {
 public:
  void EnableCompanionByPolicy(bool enable_companion_by_policy) {
    browser()->profile()->GetPrefs()->SetBoolean(
        prefs::kGoogleSearchSidePanelEnabled, enable_companion_by_policy);
  }
};

IN_PROC_BROWSER_TEST_F(CompanionPagePolicyBrowserTest,
                       SubsequentNavigationWithPolicyDefault) {
  EXPECT_TRUE(companion::IsCompanionFeatureEnabled());

  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);
  EXPECT_EQ(1u, requests_received_on_server());
}

IN_PROC_BROWSER_TEST_F(
    CompanionPagePolicyBrowserTest,
    SubsequentNavigationWithPolicyEnabledFollowedbyDisabled) {
  EXPECT_TRUE(companion::IsCompanionFeatureEnabled());

  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);
  EXPECT_EQ(1u, requests_received_on_server());

  // Disable companion by policy. CSC should not be shown anymore.
  EnableCompanionByPolicy(false);
  EXPECT_TRUE(companion::IsCompanionFeatureEnabled());
  WaitForMainPageToBeLoaded(kRelativeUrl2);
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_FALSE(side_panel_coordinator()->GetCurrentEntryId().has_value());
}

IN_PROC_BROWSER_TEST_F(CompanionPagePolicyBrowserTest,
                       PRE_SubsequentNavigationWithPolicyDisabled) {
  EnableCompanionByPolicy(false);
}

IN_PROC_BROWSER_TEST_F(CompanionPagePolicyBrowserTest,
                       SubsequentNavigationWithPolicyDisabled) {
  EXPECT_TRUE(companion::IsCompanionFeatureEnabled());
  // Load a page on the active tab and open companion side panel
  WaitForMainPageToBeLoaded(kRelativeUrl1);
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);

  EXPECT_FALSE(side_panel_coordinator()->GetCurrentEntryId().has_value());
  EXPECT_EQ(0u, requests_received_on_server());
}

IN_PROC_BROWSER_TEST_F(
    CompanionPagePolicyBrowserTest,
    PRE_SubsequentNavigationWithPolicyDisabledFollowedbyEnabled) {
  EnableCompanionByPolicy(false);
}
IN_PROC_BROWSER_TEST_F(
    CompanionPagePolicyBrowserTest,
    SubsequentNavigationWithPolicyDisabledFollowedbyEnabled) {
  // Load a page on the active tab and open companion side panel
  EXPECT_TRUE(companion::IsCompanionFeatureEnabled());
  WaitForMainPageToBeLoaded(kRelativeUrl1);
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
  EXPECT_FALSE(side_panel_coordinator()->GetCurrentEntryId().has_value());
  EXPECT_EQ(0u, requests_received_on_server());

  // Enable companion by policy and that should enable the feature.
  EnableCompanionByPolicy(true);
  EXPECT_TRUE(companion::IsCompanionFeatureEnabled());
  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);
  EXPECT_EQ(1u, requests_received_on_server());
}

class SidePanelCompanion2BrowserEnabledTest : public CompanionPageBrowserTest {
 public:
  SidePanelCompanion2BrowserEnabledTest() : CompanionPageBrowserTest() {
    enable_feature_side_panel_companion_ = true;
  }

 private:
  void SetUpFeatureList() override {
    base::FieldTrialParams enabled_params;
    enabled_params["companion-homepage-url"] =
        companion_server_.GetURL("/companion_iframe.html").spec();
    enabled_params["companion-image-upload-url"] =
        companion_server_.GetURL("/upload").spec();
    enabled_params["open-links-in-current-tab"] = ShouldOpenLinkInCurrentTab();

    std::vector<base::test::FeatureRefAndParams> enabled_features;
    std::vector<base::test::FeatureRef> disabled_features;
    if (enable_feature_lens_standalone_) {
      enabled_features.emplace_back(base::test::FeatureRefAndParams(
          lens::features::kLensStandalone, /*params*/ {}));
    } else {
      disabled_features.emplace_back(lens::features::kLensStandalone);
    }

    disabled_features.emplace_back(
        companion::features::internal::kSidePanelCompanion);
    if (enable_feature_side_panel_companion_) {
      enabled_features.emplace_back(
          companion::features::internal::kSidePanelCompanion2, enabled_params);
      feature_list_.InitWithFeaturesAndParameters(enabled_features,
                                                  disabled_features);
      if (enable_feature_lens_standalone_) {
        EXPECT_TRUE(companion::IsCompanionFeatureEnabled());
      } else {
        EXPECT_FALSE(companion::IsCompanionFeatureEnabled());
      }
    } else {
      disabled_features.emplace_back(
          companion::features::internal::kSidePanelCompanion2);
      disabled_features.emplace_back(
          companion::features::internal::
              kCompanionEnabledByObservingExpsNavigations);
      feature_list_.InitWithFeaturesAndParameters(enabled_features,
                                                  disabled_features);
      EXPECT_FALSE(companion::IsCompanionFeatureEnabled());
    }
  }
};

class SidePanelCompanion2BrowserDisabledTest
    : public SidePanelCompanion2BrowserEnabledTest {
 public:
  SidePanelCompanion2BrowserDisabledTest() {
    enable_feature_side_panel_companion_ = false;
  }
};

// Verify that Companion is disabled when `kSidePanelCompanion2` is disabled.
IN_PROC_BROWSER_TEST_F(SidePanelCompanion2BrowserDisabledTest,
                       FeatureDisabled) {
  EXPECT_FALSE(companion::IsCompanionFeatureEnabled());
  // Load a page on the active tab and open companion side panel
  WaitForMainPageToBeLoaded(kRelativeUrl1);
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);

  EXPECT_FALSE(side_panel_coordinator()->GetCurrentEntryId().has_value());
  EXPECT_EQ(0u, requests_received_on_server());
}

// TODO(crbug.com/40285326): This fails with the field trial testing config.
class SidePanelCompanion2BrowserEnabledTestNoTestingConfig
    : public SidePanelCompanion2BrowserEnabledTest {
 public:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    SidePanelCompanion2BrowserEnabledTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch("disable-field-trial-config");
  }
};

// Verify that Companion is enabled when `kSidePanelCompanion2` is enabled.
IN_PROC_BROWSER_TEST_F(SidePanelCompanion2BrowserEnabledTestNoTestingConfig,
                       FeatureEnabled) {
  EXPECT_TRUE(companion::IsCompanionFeatureEnabled());

  // Load a page on the active tab and open companion side panel
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);

  WaitForCompanionToBeLoaded();
  EXPECT_EQ(side_panel_coordinator()->GetCurrentEntryId(),
            SidePanelEntry::Id::kSearchCompanion);
  EXPECT_EQ(1u, requests_received_on_server());
}

class LensStandaloneDisabledBrowserTest : public CompanionPageBrowserTest {
 public:
  LensStandaloneDisabledBrowserTest() : CompanionPageBrowserTest() {
    enable_feature_lens_standalone_ = false;
  }
};

// Verifies the behavior when Lens standalone feature is disabled but the side
// panel Companion flag is enabled.
IN_PROC_BROWSER_TEST_F(
    LensStandaloneDisabledBrowserTest,
    CompanionFeatureStatusWhenLensStandaloneFeatureDisabled) {
  EXPECT_TRUE(base::FeatureList::IsEnabled(
      companion::features::internal::kSidePanelCompanion));
  EXPECT_FALSE(companion::IsCompanionFeatureEnabled());
}

class CompanionSidePanelPinningBrowserTest : public CompanionPageBrowserTest {
 public:
  CompanionSidePanelPinningBrowserTest() = default;

  void SetUpFeatureList() override {
    CompanionPageBrowserTest::SetUpFeatureList();
  }

  ~CompanionSidePanelPinningBrowserTest() override = default;

  void EnableCompanionByPolicy(bool enable_companion_by_policy) {
    browser()->profile()->GetPrefs()->SetBoolean(
        prefs::kGoogleSearchSidePanelEnabled, enable_companion_by_policy);
  }

};

IN_PROC_BROWSER_TEST_F(CompanionSidePanelPinningBrowserTest,
                       ActionItemDisabledOnInternalPage) {
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  actions::ActionItem* companion_action_item =
      actions::ActionManager::Get().FindAction(
          kActionSidePanelShowSearchCompanion,
          browser()->browser_actions()->root_action_item());
  EXPECT_TRUE(companion_action_item->GetEnabled());

  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(), GURL(chrome::kChromeUIExtensionsURL)));
  ASSERT_FALSE(companion_action_item->GetEnabled());
}

IN_PROC_BROWSER_TEST_F(CompanionSidePanelPinningBrowserTest,
                       ActionItemChangeOnPolicyChange) {
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
  actions::ActionItem* companion_action_item =
      actions::ActionManager::Get().FindAction(
          kActionSidePanelShowSearchCompanion,
          browser()->browser_actions()->root_action_item());
  EXPECT_TRUE(companion_action_item->GetEnabled());

  EnableCompanionByPolicy(false);
  EXPECT_FALSE(companion_action_item->GetVisible());

  EnableCompanionByPolicy(true);
  EXPECT_TRUE(companion_action_item->GetVisible());
}