| // Copyright 2025 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/glic/e2e_test/glic_e2e_test.h" |
| |
| #include <map> |
| #include <optional> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/path_service.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/contextual_cueing/contextual_cueing_features.h" |
| #include "chrome/browser/glic/fre/fre_util.h" |
| #include "chrome/browser/glic/fre/glic_fre_dialog_view.h" |
| #include "chrome/browser/glic/glic_pref_names.h" |
| #include "chrome/browser/glic/host/glic_features.mojom.h" |
| #include "chrome/browser/glic/host/guest_util.h" |
| #include "chrome/browser/glic/public/glic_keyed_service.h" |
| #include "chrome/browser/glic/public/glic_keyed_service_factory.h" |
| #include "chrome/browser/glic/test_support/glic_test_util.h" |
| #include "chrome/browser/glic/test_support/interactive_test_util.h" |
| #include "chrome/browser/glic/widget/glic_view.h" |
| #include "chrome/browser/glic/widget/glic_window_controller.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/signin/e2e_tests/live_test.h" |
| #include "chrome/browser/signin/e2e_tests/signin_util.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_element_identifiers.h" |
| #include "chrome/browser/ui/ui_features.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/signin/public/identity_manager/test_accounts.h" |
| #include "components/sync/base/features.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/test_devtools_protocol_client.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "services/network/public/cpp/network_switches.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/interaction/interactive_test.h" |
| #include "ui/compositor/scoped_animation_duration_scale_mode.h" |
| |
| #if ENABLE_GLIC_INTERNAL_TESTS |
| #include "chrome/browser/glic/e2e_test/internal/constants.h" |
| #else |
| #include "chrome/browser/glic/e2e_test/internal_test_placeholder_constants.h" // nogncheck |
| #endif |
| |
| namespace glic::test { |
| |
| namespace { |
| |
| using glic::test::internal::kGlicFreShowingDialogState; |
| using glic::test::internal::kGlicWindowControllerState; |
| |
| constexpr base::FilePath::StringViewType kRecordingDirectoryPath = |
| FILE_PATH_LITERAL("chrome/browser/glic/e2e_test/internal/wpr_recordings"); |
| |
| const char kGlicE2ETestModeSwitch[] = "glic-e2e-test-mode"; |
| const char kHostResolverRulesValue[] = |
| "MAP *:80 127.0.0.1:8080,MAP *:443 127.0.0.1:8081,EXCLUDE localhost"; |
| constexpr char kEnableActorTests[] = "enable-actor-tests"; |
| const char kEnableLowBandwidthTestsSwitch[] = "enable-low-bandwidth-tests"; |
| |
| // The first 2 is from WPR code readme. The last one is from |
| // |kWebPageReplayCertSPKI| in |
| // //chrome/browser/autofill/captured_sites_test_utils.cc |
| // TODO(crbug.com/399665693): Consolidate the wpr RSA certs in wpr source code |
| // and used in the C++ test utilities. |
| const char kIgnoreCertificateErrorsSPKIListValue[] = |
| "PhrPvGIaAMmd29hj8BCZOq096yj7uMpRNHpn5PDxI6I=," |
| "2HcXCSKKJS0lEXLQEWhpHUfGuojiU0tiT5gOF9LP6IQ=," |
| "PoNnQAwghMiLUPg1YNFtvTfGreNT8r9oeLEyzgNCJWc="; |
| } // namespace |
| |
| GlicE2ETest::GlicE2ETest() { |
| // TODO(https://crbug.com/440578183): ZeroStateSuggestionsV2 is enabled here |
| // due to the associated bug and should be removed here once fixed. |
| scoped_feature_list_.InitWithFeatures( |
| /*enabled_features=*/{features::kGlic, features::kTabstripComboButton, |
| features::kGlicKeyboardShortcutNewBadge, |
| features::kGlicRollout, |
| contextual_cueing::kContextualCueing, |
| mojom::features::kZeroStateSuggestionsV2}, |
| /*disabled_features=*/{syncer::kReplaceSyncPromosWithSignInPromos}); |
| } |
| |
| GlicE2ETest::~GlicE2ETest() = default; |
| |
| void GlicE2ETest::SetUp() { |
| const base::CommandLine* command_line_of_test = |
| base::CommandLine::ForCurrentProcess(); |
| |
| std::string test_mode_value = |
| command_line_of_test->GetSwitchValueASCII(kGlicE2ETestModeSwitch); |
| |
| running_actor_tests_ = command_line_of_test->HasSwitch(kEnableActorTests); |
| enable_low_bandwidth_tests_ = |
| command_line_of_test->HasSwitch(kEnableLowBandwidthTestsSwitch); |
| |
| if (test_mode_value.empty() || test_mode_value == "real_backend") { |
| test_mode_ = kRealBackend; |
| } else if (test_mode_value == "record") { |
| test_mode_ = kRecord; |
| } else if (test_mode_value == "replay") { |
| test_mode_ = kReplay; |
| } else { |
| FAIL() << "Incorrect test mode input: %s" << test_mode_value; |
| } |
| |
| if (test_mode_ == kRecord || test_mode_ == kReplay) { |
| web_page_replay_server_wrapper_ = |
| std::make_unique<captured_sites_test_utils::WebPageReplayServerWrapper>( |
| test_mode_ == kReplay, 8080, 8081, kWprArguments); |
| } |
| |
| // Always disable animation for stability. |
| ui::ScopedAnimationDurationScaleMode disable_animation( |
| ui::ScopedAnimationDurationScaleMode::ZERO_DURATION); |
| LiveTest::SetUp(); |
| } |
| |
| void GlicE2ETest::SetUpCommandLine(base::CommandLine* command_line) { |
| LiveTest::SetUpCommandLine(command_line); |
| |
| if (test_mode_ == kRecord || test_mode_ == kReplay) { |
| // The following arguments make browser work with WPR proxy. |
| command_line->AppendSwitchASCII(network::switches::kHostResolverRules, |
| kHostResolverRulesValue); |
| command_line->AppendSwitchASCII( |
| network::switches::kIgnoreCertificateErrorsSPKIList, |
| kIgnoreCertificateErrorsSPKIListValue); |
| } |
| } |
| |
| void GlicE2ETest::PreRunTestOnMainThread() { |
| LiveTest::PreRunTestOnMainThread(); |
| |
| GURL glic_fre_url = glic::GetFreURL(browser()->profile()); |
| GURL glic_guest_url = glic::GetGuestURL(); |
| CHECK(glic_fre_url.is_valid() && glic_guest_url.is_valid()) |
| << "Incorrect GLiC guest or FRE URL in cmd line arguments."; |
| |
| if (test_mode_ == kRecord || test_mode_ == kReplay) { |
| // When WPR is used, for consistency, require consistent host and path. |
| CHECK(base::Contains(glic_fre_url.spec(), kAllowedHostAndPathForWpr) && |
| base::Contains(glic_guest_url.spec(), kAllowedHostAndPathForWpr)) |
| << "Please use allowed URL for WPR."; |
| } |
| } |
| |
| void GlicE2ETest::LoginTestAccountOrForceFakeSignin() { |
| if (test_mode_ == kRealBackend || test_mode_ == kRecord) { |
| std::optional<signin::TestAccountSigninCredentials> test_account = |
| GetTestAccounts()->GetAccount( |
| running_actor_tests_ ? kTestActorAccountLabel : kTestAccountLabel); |
| signin::test::SignInFunctions sign_in_functions = |
| signin::test::SignInFunctions( |
| base::BindLambdaForTesting( |
| [this]() -> Browser* { return this->browser(); }), |
| base::BindLambdaForTesting( |
| [this](int index, const GURL& url, |
| ui::PageTransition transition) -> bool { |
| return this->AddTabAtIndex(index, url, transition); |
| })); |
| // Sign in to opted in test account. |
| CHECK(test_account.has_value()); |
| sign_in_functions.TurnOnSync(*test_account, 0); |
| } else { |
| SigninWithPrimaryAccount(browser()->profile()); |
| SetModelExecutionCapability(browser()->profile(), true); |
| } |
| } |
| |
| void GlicE2ETest::SetFRECompletion() { |
| ::glic::SetFRECompletion(browser()->profile(), prefs::FreStatus::kCompleted); |
| } |
| |
| void GlicE2ETest::SetUpInProcessBrowserTestFixture() { |
| // Allowlists hosts. |
| host_resolver()->AllowDirectLookup("*.google.com"); |
| |
| LiveTest::SetUpInProcessBrowserTestFixture(); |
| } |
| |
| void GlicE2ETest::TearDownOnMainThread() { |
| for (auto& client : devtools_clients_) { |
| client.second->DetachProtocolClient(); |
| } |
| devtools_clients_.clear(); |
| if (test_mode_ == kRecord || test_mode_ == kReplay) { |
| // Ensure enough time for WPR to write archive at recording mode |
| // by putting this in main thread. |
| EXPECT_TRUE(web_page_replay_server_wrapper_->Stop()) |
| << "Cannot stop the local Web Page Replay server."; |
| } |
| LiveTest::TearDownOnMainThread(); |
| } |
| |
| ui::test::InteractiveTestApi::MultiStep GlicE2ETest::WaitForAndInstrumentFre() { |
| MultiStep steps(Steps( |
| UninstrumentWebContents(kGlicFreContentsElementId, false), |
| UninstrumentWebContents(kGlicFreHostElementId, false), |
| InAnyContext( |
| ObserveState(kGlicFreShowingDialogState, std::ref(fre_controller())), |
| WaitForState(kGlicFreShowingDialogState, true), |
| Steps(InstrumentNonTabWebView( |
| kGlicFreHostElementId, |
| GlicFreDialogView::kWebViewElementIdForTesting), |
| InstrumentInnerWebContents(kGlicFreContentsElementId, |
| kGlicFreHostElementId, 0), |
| WaitForWebContentsReady(kGlicFreContentsElementId)), |
| StopObservingState(kGlicFreShowingDialogState)))); |
| |
| AddDescriptionPrefix(steps, "WaitForAndInstrumentFre"); |
| return steps; |
| } |
| |
| ui::test::InteractiveTestApi::MultiStep |
| GlicE2ETest::WaitForAndInstrumentGlic() { |
| MultiStep steps(Steps( |
| UninstrumentWebContents(kGlicContentsElementId, false), |
| UninstrumentWebContents(kGlicHostElementId, false), |
| InAnyContext( |
| ObserveState(kGlicWindowControllerState, |
| std::ref(window_controller())), |
| WaitForState(kGlicWindowControllerState, |
| GlicWindowController::State::kOpen), |
| Steps(InstrumentNonTabWebView(kGlicHostElementId, kGlicViewElementId), |
| InstrumentInnerWebContents(kGlicContentsElementId, |
| kGlicHostElementId, 0), |
| WaitForWebContentsReady(kGlicContentsElementId)), |
| StopObservingState(kGlicWindowControllerState)))); |
| |
| AddDescriptionPrefix(steps, "WaitForAndInstrumentGlic"); |
| return steps; |
| } |
| |
| void GlicE2ETest::MaybeStartWebPageReplayForRecordingPath( |
| const std::string recording_filename) { |
| if (test_mode_ == kRealBackend) { |
| return; |
| } |
| base::FilePath root_path; |
| base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &root_path); |
| base::FilePath recording_dir_path = |
| base::MakeAbsoluteFilePath(root_path.Append(kRecordingDirectoryPath)); |
| base::FilePath recording_path = recording_dir_path.Append( |
| base::FilePath::FromUTF8Unsafe(recording_filename)); |
| if (test_mode_ == kReplay) { |
| CHECK(base::PathExists(recording_path)) |
| << recording_filename << " does not exist."; |
| } |
| |
| ASSERT_TRUE(web_page_replay_server_wrapper()->Start(recording_path)); |
| } |
| |
| GlicKeyedService* GlicE2ETest::glic_service() { |
| return GlicKeyedServiceFactory::GetGlicKeyedService( |
| InProcessBrowserTest::browser()->GetProfile()); |
| } |
| GlicWindowController& GlicE2ETest::window_controller() { |
| return glic_service()->window_controller(); |
| } |
| GlicFreController& GlicE2ETest::fre_controller() { |
| return glic_service()->fre_controller(); |
| } |
| WebPageReplayServerWrapper* GlicE2ETest::web_page_replay_server_wrapper() { |
| return web_page_replay_server_wrapper_.get(); |
| } |
| |
| void GlicE2ETest::ThrottleCurrentTabNetwork() { |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| CHECK(web_contents); |
| ThrottleWebContentsNetwork(web_contents); |
| } |
| |
| void GlicE2ETest::ThrottleWebContentsNetwork( |
| content::WebContents* web_contents) { |
| CHECK(web_contents); |
| |
| auto& devtools_client_ptr = devtools_clients_[web_contents]; |
| if (!devtools_client_ptr) { |
| devtools_client_ptr = |
| std::make_unique<content::TestDevToolsProtocolClient>(); |
| devtools_client_ptr->AttachToWebContents(web_contents); |
| devtools_client_ptr->SendCommand("Network.enable", base::Value::Dict()); |
| } |
| |
| // Corresponds to the "Slow 3G" preset in |
| // third_party/devtools-frontend/src/front_end/core/sdk/NetworkManager.ts |
| base::Value::Dict params; |
| params.Set("offline", false); |
| // Latency in ms. |
| params.Set("latency", 2000.0); |
| // Throughput in Bps. |
| params.Set("downloadThroughput", 50000); |
| params.Set("uploadThroughput", 50000); |
| |
| devtools_client_ptr->SendCommand("Network.emulateNetworkConditions", |
| std::move(params)); |
| } |
| |
| void GlicE2ETest::ThrottleGlicNetwork() { |
| auto* glic_service = |
| glic::GlicKeyedServiceFactory::GetGlicKeyedService(browser()->profile()); |
| for (auto* host : glic_service->host_manager().GetAllHosts()) { |
| auto* webui_contents = host->webui_contents(); |
| if (webui_contents) { |
| content::WebContents* inner_contents = |
| webui_contents->GetInnerWebContents()[0]; |
| CHECK(inner_contents); |
| ThrottleWebContentsNetwork(inner_contents); |
| } |
| } |
| } |
| |
| } // namespace glic::test |