| // 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 <sstream> |
| |
| #include "base/scoped_observation.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/to_string.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/metrics/user_action_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/glic/host/glic.mojom-shared.h" |
| #include "chrome/browser/glic/host/glic_ui.h" |
| #include "chrome/browser/glic/test_support/interactive_glic_test.h" |
| #include "chrome/browser/glic/test_support/interactive_test_util.h" |
| #include "chrome/browser/glic/widget/glic_window_controller.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/profiles/profile_test_util.h" |
| #include "chrome/browser/ui/browser_element_identifiers.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/pref_names.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/no_renderer_crashes_assertion.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "ui/base/accelerators/accelerator.h" |
| #include "ui/base/interaction/element_identifier.h" |
| #include "ui/base/interaction/state_observer.h" |
| |
| namespace glic { |
| |
| namespace { |
| |
| using mojom::WebUiState; |
| |
| class GlicUiStateHistoryObserver |
| : public ui::test::StateObserver<std::vector<WebUiState>>, |
| public Host::Observer { |
| public: |
| explicit GlicUiStateHistoryObserver(Host* host) : host_(*host) { |
| states_.push_back(host->GetPrimaryWebUiState()); |
| host->AddObserver(this); |
| } |
| |
| ~GlicUiStateHistoryObserver() override { host_->RemoveObserver(this); } |
| |
| ValueType GetStateObserverInitialState() const override { return states_; } |
| |
| private: |
| void WebUiStateChanged(WebUiState state) override { |
| states_.push_back(state); |
| OnStateObserverStateChanged(states_); |
| } |
| |
| const raw_ref<Host> host_; |
| ValueType states_; |
| }; |
| |
| DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(GlicUiStateHistoryObserver, |
| kGlicUiStateHistory); |
| |
| class ContextAccessIndicatorObserver |
| : public ui::test::StateObserver<std::vector<bool>> { |
| public: |
| explicit ContextAccessIndicatorObserver(GlicKeyedService* service) { |
| scoped_subscription_ = |
| service->AddContextAccessIndicatorStatusChangedCallback( |
| base::BindRepeating(&ContextAccessIndicatorObserver:: |
| ContextAccessIndicatorStatusChanged, |
| base::Unretained(this))); |
| } |
| |
| ~ContextAccessIndicatorObserver() override = default; |
| |
| ValueType GetStateObserverInitialState() const override { return states_; } |
| |
| private: |
| void ContextAccessIndicatorStatusChanged(bool status) { |
| states_.push_back(status); |
| OnStateObserverStateChanged(states_); |
| } |
| |
| base::CallbackListSubscription scoped_subscription_; |
| ValueType states_; |
| }; |
| |
| DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(ContextAccessIndicatorObserver, |
| kGlicContextAccessIndicatorHistory); |
| |
| // Specifies artificial parameters for how network and loading should behave for |
| // tests in this file. |
| struct TestParams { |
| TestParams() = default; |
| explicit TestParams(bool connected) : start_connected(connected) {} |
| TestParams(base::TimeDelta time_before_loading_page_, |
| base::TimeDelta min_loading_page_duration_, |
| base::TimeDelta max_loading_page_duration_, |
| base::TimeDelta loading_delay_) |
| : time_before_loading_page(time_before_loading_page_), |
| min_loading_page_duration(min_loading_page_duration_), |
| max_loading_page_duration(max_loading_page_duration_), |
| loading_delay(loading_delay_) {} |
| ~TestParams() = default; |
| |
| // Time before loading page shows. |
| std::optional<base::TimeDelta> time_before_loading_page; |
| // Minimum time loading page shows. |
| std::optional<base::TimeDelta> min_loading_page_duration; |
| // Maximum time loading page shows before error. |
| std::optional<base::TimeDelta> max_loading_page_duration; |
| // Time to load the glic UI. |
| std::optional<base::TimeDelta> loading_delay; |
| // Whether the page believes it has network at startup. |
| bool start_connected = true; |
| |
| base::FieldTrialParams GetFieldTrialParams() const { |
| base::FieldTrialParams params; |
| if (time_before_loading_page) { |
| params.emplace( |
| features::kGlicPreLoadingTimeMs.name, |
| base::StringPrintf("%u", time_before_loading_page->InMilliseconds())); |
| } |
| if (min_loading_page_duration) { |
| params.emplace(features::kGlicMinLoadingTimeMs.name, |
| base::StringPrintf( |
| "%u", min_loading_page_duration->InMilliseconds())); |
| } |
| if (max_loading_page_duration) { |
| params.emplace(features::kGlicMaxLoadingTimeMs.name, |
| base::StringPrintf( |
| "%u", max_loading_page_duration->InMilliseconds())); |
| } |
| return params; |
| } |
| }; |
| |
| const ui::Accelerator escape_key(ui::VKEY_ESCAPE, ui::EF_NONE); |
| |
| } // namespace |
| |
| // Base class that sets up network connection mode and timeouts based on |
| // `TestParams` (see above). |
| class GlicUiInteractiveUiTestBase : public test::InteractiveGlicTest { |
| public: |
| explicit GlicUiInteractiveUiTestBase(const TestParams& params) |
| : InteractiveGlicTestMixin(params.GetFieldTrialParams()) { |
| if (!params.start_connected) { |
| GlicUI::simulate_no_connection_for_testing(); |
| } |
| if (params.loading_delay) { |
| std::ostringstream oss; |
| oss << params.loading_delay->InMilliseconds(); |
| add_mock_glic_query_param("delay_ms", oss.str()); |
| } |
| } |
| ~GlicUiInteractiveUiTestBase() override = default; |
| |
| auto CheckElementVisible(const DeepQuery& where, bool visible) { |
| MultiStep steps; |
| if (visible) { |
| steps = |
| InAnyContext(WaitForElementVisible(test::kGlicHostElementId, where)); |
| } |
| steps += InAnyContext(CheckJsResultAt(test::kGlicHostElementId, where, |
| "(el) => el.hidden", |
| testing::Ne(visible))); |
| AddDescriptionPrefix(steps, "CheckElementVisible"); |
| return steps; |
| } |
| |
| auto CheckMockElementChecked(const DeepQuery& where, bool checked) { |
| MultiStep steps = Steps(InAnyContext(WaitForElementVisible( |
| test::kGlicContentsElementId, {"body"})), |
| InAnyContext(CheckJsResultAt( |
| test::kGlicContentsElementId, where, |
| "(el) => el.checked", testing::Eq(checked)))); |
| AddDescriptionPrefix(steps, "CheckElementChecked"); |
| return steps; |
| } |
| |
| auto CheckEscapeKeyDismisses(const DeepQuery& panel) { |
| return InAnyContext( |
| WaitForShow(test::kGlicHostElementId), CheckElementVisible(panel, true), |
| InSameContext(SendAccelerator(test::kGlicHostElementId, escape_key) |
| .SetMustRemainVisible(false), |
| WaitForHide(kGlicViewElementId)), |
| CheckControllerHasWidget(false)); |
| } |
| |
| auto ChangeConnectionState(bool online) { |
| return ExecuteJs(test::kGlicHostElementId, |
| base::StringPrintf(R"( |
| function () { |
| window.appController.simulateNoConnection = %s; |
| window.appController.%s(); |
| } |
| )", |
| base::ToString(!online), |
| online ? "online" : "offline")); |
| } |
| |
| static auto CurrentStateMatcher(testing::Matcher<WebUiState> target) { |
| return testing::AllOf( |
| testing::Not(testing::IsEmpty()), |
| testing::Property(&GlicUiStateHistoryObserver::ValueType::back, |
| target)); |
| } |
| |
| static auto IsCurrently(WebUiState state) { |
| return CurrentStateMatcher(state); |
| } |
| |
| static auto IsNotCurrently(WebUiState state) { |
| return CurrentStateMatcher(testing::Ne(state)); |
| } |
| |
| static auto IsContextAccessIndicatorCurrently(bool showing) { |
| return testing::AllOf( |
| testing::Not(testing::IsEmpty()), |
| testing::Property(&ContextAccessIndicatorObserver::ValueType::back, |
| showing)); |
| } |
| |
| const DeepQuery kOfflinePanel = {"#offlinePanel"}; |
| const DeepQuery kLoadingPanel = {"#loadingPanel"}; |
| const DeepQuery kErrorPanel = {"#errorPanel"}; |
| const DeepQuery kContentsPanel = {"#guestPanel"}; |
| }; |
| |
| class GlicUiInteractiveTest : public GlicUiInteractiveUiTestBase { |
| public: |
| GlicUiInteractiveTest() |
| : GlicUiInteractiveUiTestBase(TestParams(/*connected=*/true)) {} |
| ~GlicUiInteractiveTest() override = default; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(GlicUiInteractiveTest, OpenGlicWindow) { |
| base::HistogramTester histogram_tester; |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| OpenGlicWindow(GlicWindowMode::kDetached, GlicInstrumentMode::kHostOnly)); |
| // The browser is active when opening the Glic window. |
| histogram_tester.ExpectUniqueSample("Glic.Session.Open.BrowserActiveState", |
| 0 /*kBrowserActive*/, 1); |
| } |
| |
| // Tests the network being connected at startup (as normal). |
| class GlicUiConnectedUiTest : public GlicUiInteractiveUiTestBase, |
| public testing::WithParamInterface<bool> { |
| public: |
| GlicUiConnectedUiTest() |
| : GlicUiInteractiveUiTestBase(TestParams(/*connected=*/true)) { |
| if (IsDetachedOnlyModeEnabled()) { |
| feature_list_.InitAndEnableFeature(features::kGlicDetached); |
| } else { |
| feature_list_.InitAndDisableFeature(features::kGlicDetached); |
| } |
| } |
| ~GlicUiConnectedUiTest() override = default; |
| |
| bool IsDetachedOnlyModeEnabled() const { return GetParam(); } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, GlicUiConnectedUiTest, testing::Bool()); |
| |
| IN_PROC_BROWSER_TEST_P(GlicUiConnectedUiTest, DisconnectedPanelHidden) { |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly), |
| WaitForState(kGlicUiStateHistory, IsNotCurrently(WebUiState::kOffline)), |
| CheckElementVisible(kOfflinePanel, false)); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(GlicUiConnectedUiTest, |
| DoesNotHidePanelWhenReadyButOffline) { |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly), |
| WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kReady)), |
| ChangeConnectionState(false), CheckElementVisible(kContentsPanel, true), |
| CheckState(kGlicUiStateHistory, IsCurrently(WebUiState::kReady))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(GlicUiConnectedUiTest, CanAttachWithBrowserWindow) { |
| if (IsDetachedOnlyModeEnabled()) { |
| GTEST_SKIP(); |
| } |
| RunTestSequence(OpenGlicWindow(GlicWindowMode::kDetached, |
| GlicInstrumentMode::kHostAndContents), |
| CheckMockElementChecked({"#canAttachCheckbox"}, true)); |
| } |
| |
| // DISABLED: Not reliable yet. |
| IN_PROC_BROWSER_TEST_P(GlicUiConnectedUiTest, |
| DISABLED_CanNotAttachWithMinimizedBrowser) { |
| RunTestSequence( |
| OpenGlicWindow(GlicWindowMode::kDetached, |
| GlicInstrumentMode::kHostAndContents), |
| CheckMockElementChecked({"#canAttachCheckbox"}, true), |
| Do([&]() { browser()->GetBrowserView().Minimize(); }), |
| // TODO(harringtond): Ideally this would wait until not checked, rather |
| // than check only once. There's no guarantee the web client |
| // has been updated before this code runs. Currently, this |
| // test works, though it's a risk for flakiness. |
| CheckMockElementChecked({"#canAttachCheckbox"}, false)); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(GlicUiConnectedUiTest, |
| DoesNotNavigateToUnsupportedOrigin) { |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| OpenGlicWindow(GlicWindowMode::kAttached, |
| GlicInstrumentMode::kHostAndContents), |
| WaitForElementVisible(test::kGlicContentsElementId, {"body"}), |
| InAnyContext(ExecuteJs(test::kGlicContentsElementId, |
| R"js(()=>{location = 'http://b.test/page';})js")), |
| // Just wait a bit and make sure the page doesn't navigate. |
| InAnyContext(CheckJsResult(test::kGlicContentsElementId, R"js( |
| ()=>{ |
| const {promise, resolve} = Promise.withResolvers(); |
| window.setTimeout(() => resolve(true), 1000); |
| return promise; |
| })js"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(GlicUiConnectedUiTest, |
| HidesTabAccessUIOnWebClientCrash) { |
| content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes; |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| ObserveState(kGlicContextAccessIndicatorHistory, glic_service()), |
| OpenGlicWindow(GlicWindowMode::kAttached, |
| GlicInstrumentMode::kHostAndContents), |
| WaitForElementVisible(test::kGlicContentsElementId, {"body"}), |
| InAnyContext(ExecuteJs( |
| test::kGlicContentsElementId, |
| R"js(()=>{client.browser.setContextAccessIndicator(true);})js")), |
| InAnyContext(WaitForState(kGlicContextAccessIndicatorHistory, |
| IsContextAccessIndicatorCurrently(true))), |
| // Kills the web client process, simulating a renderer crash. |
| Do([this] { FindGlicGuestMainFrame()->GetProcess()->Shutdown(0); }), |
| InAnyContext( |
| WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kError))), |
| InAnyContext(WaitForState(kGlicContextAccessIndicatorHistory, |
| IsContextAccessIndicatorCurrently(false)))); |
| } |
| |
| // Tests the network being unavailable at startup. |
| class GlicUiDisconnectedUiTest : public GlicUiInteractiveUiTestBase { |
| public: |
| GlicUiDisconnectedUiTest() |
| : GlicUiInteractiveUiTestBase(TestParams(/*connected=*/false)) {} |
| ~GlicUiDisconnectedUiTest() override = default; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(GlicUiDisconnectedUiTest, DisconnectedPanelShown) { |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly), |
| WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kOffline)), |
| CheckElementVisible(kOfflinePanel, true)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlicUiDisconnectedUiTest, LoadsWhenBackOnline) { |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly), |
| ChangeConnectionState(true), |
| WaitForState(kGlicUiStateHistory, IsNotCurrently(WebUiState::kOffline)), |
| WaitForElementVisible(test::kGlicHostElementId, kContentsPanel), |
| CheckElementVisible(kOfflinePanel, false), |
| CheckState(kGlicUiStateHistory, IsCurrently(WebUiState::kReady))); |
| } |
| |
| // Tests the entire loading sequence through to error if Glic is slow to load. |
| class GlicUiFullLoadingSequenceTest : public GlicUiInteractiveUiTestBase { |
| public: |
| GlicUiFullLoadingSequenceTest() |
| : GlicUiInteractiveUiTestBase( |
| TestParams(base::Milliseconds(250), // Pre-load |
| base::Milliseconds(250), // Min loading time |
| base::Milliseconds(500), // Max loading time |
| base::Hours(1))) // Actual loading time |
| {} |
| ~GlicUiFullLoadingSequenceTest() override = default; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(GlicUiFullLoadingSequenceTest, Test) { |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly), |
| WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kError)), |
| CheckElementVisible(kErrorPanel, true), |
| CheckElementVisible(kContentsPanel, false), |
| CheckState(kGlicUiStateHistory, |
| testing::ElementsAre( |
| WebUiState::kUninitialized, WebUiState::kBeginLoad, |
| WebUiState::kShowLoading, WebUiState::kFinishLoading, |
| WebUiState::kError))); |
| } |
| |
| // Tests the loading sequence where Glic loads almost immediately and there is |
| // no hold. |
| class GlicUiQuickLoadingSequenceNoHoldTest |
| : public GlicUiInteractiveUiTestBase { |
| public: |
| GlicUiQuickLoadingSequenceNoHoldTest() |
| : GlicUiInteractiveUiTestBase( |
| TestParams(base::Milliseconds(250), // Pre-load |
| base::Milliseconds(0), // Min loading time |
| base::Hours(1), // Max loading time |
| base::Seconds(1))) // Actual loading time |
| {} |
| ~GlicUiQuickLoadingSequenceNoHoldTest() override = default; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(GlicUiQuickLoadingSequenceNoHoldTest, Test) { |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly), |
| WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kReady)), |
| CheckElementVisible(kContentsPanel, true), |
| CheckState(kGlicUiStateHistory, |
| testing::ElementsAre( |
| WebUiState::kUninitialized, WebUiState::kBeginLoad, |
| WebUiState::kShowLoading, WebUiState::kFinishLoading, |
| WebUiState::kReady))); |
| } |
| |
| // Tests the loading sequence where Glic loads almost immediately and there is a |
| // hold. |
| class GlicUiQuickLoadingSequenceWithHoldTest |
| : public GlicUiInteractiveUiTestBase { |
| public: |
| GlicUiQuickLoadingSequenceWithHoldTest() |
| : GlicUiInteractiveUiTestBase( |
| TestParams(base::Milliseconds(0), // Pre-load |
| base::Seconds(5), // Min loading time |
| base::Hours(1), // Max loading time |
| base::Milliseconds(500))) // Actual loading time |
| {} |
| ~GlicUiQuickLoadingSequenceWithHoldTest() override = default; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(GlicUiQuickLoadingSequenceWithHoldTest, Test) { |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly), |
| WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kReady)), |
| CheckElementVisible(kContentsPanel, true), |
| CheckState( |
| kGlicUiStateHistory, |
| testing::ElementsAre(WebUiState::kUninitialized, |
| WebUiState::kBeginLoad, WebUiState::kShowLoading, |
| WebUiState::kHoldLoading, WebUiState::kReady))); |
| } |
| |
| // Tests the loading sequence where Glic loads almost immediately during |
| // preload. |
| class GlicUiQuickLoadingSequenceWithPreloadTest |
| : public GlicUiInteractiveUiTestBase { |
| public: |
| GlicUiQuickLoadingSequenceWithPreloadTest() |
| : GlicUiInteractiveUiTestBase( |
| TestParams(base::Seconds(3), // Pre-load |
| base::Seconds(5), // Min loading time |
| base::Seconds(10), // Max loading time |
| base::Milliseconds(10))) // Actual loading time |
| {} |
| ~GlicUiQuickLoadingSequenceWithPreloadTest() override = default; |
| }; |
| |
| // See https://crbug.com/418639389 - these probably need to be broken into unit |
| // tests with only integration tests for the messaging being passed back and |
| // forth; slow test runners can cause any time limit to be overrun. |
| IN_PROC_BROWSER_TEST_F(GlicUiQuickLoadingSequenceWithPreloadTest, |
| DISABLED_Test) { |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly), |
| WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kReady)), |
| CheckElementVisible(kContentsPanel, true), |
| CheckState( |
| kGlicUiStateHistory, |
| testing::ElementsAre(WebUiState::kUninitialized, |
| WebUiState::kBeginLoad, WebUiState::kReady))); |
| } |
| |
| // Tests the loading panel is visible during load. |
| class GlicUiLoadingPanelWaitingTest : public GlicUiInteractiveUiTestBase { |
| public: |
| GlicUiLoadingPanelWaitingTest() |
| : GlicUiInteractiveUiTestBase( |
| TestParams(base::Milliseconds(0), // Pre-load |
| base::Milliseconds(0), // Min loading time |
| base::Hours(1), // Max loading time |
| base::Hours(1))) // Actual loading time |
| {} |
| ~GlicUiLoadingPanelWaitingTest() override = default; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(GlicUiLoadingPanelWaitingTest, Test) { |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly), |
| WaitForState(kGlicUiStateHistory, |
| IsCurrently(WebUiState::kFinishLoading)), |
| CheckElementVisible(kLoadingPanel, true)); |
| } |
| |
| // Tests the loading panel is visible during hold. |
| class GlicUiLoadingPanelHoldingTest : public GlicUiInteractiveUiTestBase { |
| public: |
| GlicUiLoadingPanelHoldingTest() |
| : GlicUiInteractiveUiTestBase( |
| TestParams(base::Milliseconds(0), // Pre-load |
| base::Hours(1), // Min loading time |
| base::Hours(2), // Max loading time |
| base::Milliseconds(500))) // Actual loading time |
| {} |
| ~GlicUiLoadingPanelHoldingTest() override = default; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(GlicUiLoadingPanelHoldingTest, Test) { |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly), |
| WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kHoldLoading)), |
| CheckElementVisible(kLoadingPanel, true)); |
| } |
| |
| // Test that the escape key can be used to dismiss the floaty window in various |
| // loading and error states. |
| |
| IN_PROC_BROWSER_TEST_F(GlicUiDisconnectedUiTest, EscapeKeyDismisses) { |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly), |
| WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kOffline)), |
| CheckEscapeKeyDismisses(kOfflinePanel)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlicUiLoadingPanelWaitingTest, EscapeKeyDismisses) { |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly), |
| WaitForState(kGlicUiStateHistory, |
| IsCurrently(WebUiState::kFinishLoading)), |
| CheckEscapeKeyDismisses(kLoadingPanel)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlicUiLoadingPanelHoldingTest, EscapeKeyDismisses) { |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly), |
| WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kHoldLoading)), |
| CheckEscapeKeyDismisses(kLoadingPanel)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(GlicUiFullLoadingSequenceTest, EscapeKeyDismisses) { |
| RunTestSequence( |
| ObserveState(kGlicUiStateHistory, GetHost()), |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly), |
| WaitForState(kGlicUiStateHistory, IsCurrently(WebUiState::kError)), |
| CheckEscapeKeyDismisses(kErrorPanel)); |
| } |
| |
| class GlicWithMultipleProfilesTest : public GlicUiInteractiveUiTestBase { |
| public: |
| GlicWithMultipleProfilesTest() : GlicUiInteractiveUiTestBase({}) {} |
| ~GlicWithMultipleProfilesTest() override = default; |
| |
| Browser* CreateBrowserWithNewProfile() { |
| ProfileManager* profile_manager = g_browser_process->profile_manager(); |
| base::FilePath new_path = |
| profile_manager->GenerateNextProfileDirectoryPath(); |
| Profile& new_profile = |
| profiles::testing::CreateProfileSync(profile_manager, new_path); |
| |
| return CreateBrowser(&new_profile); |
| } |
| }; |
| |
| // Creates two browsers with different profiles. Opens glic in each and verifies |
| // it loads, doesn't crash, and hides the other glic window. |
| IN_PROC_BROWSER_TEST_F(GlicWithMultipleProfilesTest, OpenGlicInEachProfile) { |
| Browser* first_browser = browser(); |
| Browser* second_browser = CreateBrowserWithNewProfile(); |
| SetActiveBrowser(second_browser); |
| |
| RunTestSequence( |
| // Warning!: `kAttached` really just clicks the glic button, the window |
| // will open in detached mode because `features::kGlicDetached` is |
| // enabled. We do this because InteractiveGlicTestMixin::ToggleGlicWindow |
| // doesn't work right in detached mode with multiple profiles. |
| // TODO(b/418284946): Fix ToggleGlicWindow. |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly)); |
| |
| SetActiveBrowser(first_browser); |
| RunTestSequence( |
| CheckControllerShowing(false), |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly)); |
| |
| SetActiveBrowser(second_browser); |
| RunTestSequence( |
| CheckControllerShowing(false), |
| OpenGlicWindow(GlicWindowMode::kAttached, GlicInstrumentMode::kHostOnly)); |
| } |
| |
| DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(ui::test::PollingStateObserver<GURL>, |
| kOpenedTabUrlState); |
| |
| class GlicApiUiRedirectTest : public test::InteractiveGlicTest, |
| public testing::WithParamInterface<bool> { |
| public: |
| void SetUp() override { |
| admin_hostname_ = |
| GetParam() ? "admin.google.com" : "access.workspace.google.com"; |
| |
| embedded_https_test_server().SetCertHostnames( |
| {admin_hostname_, "gemini.google.com", "127.0.0.1"}); |
| embedded_https_test_server().AddDefaultHandlers(); |
| ASSERT_TRUE(embedded_https_test_server().InitializeAndListen()); |
| |
| GURL admin_url_base = embedded_https_test_server().GetURL("/echo?"); |
| GURL::Replacements replacements; |
| replacements.SetHostStr(admin_hostname_); |
| GURL admin_url = admin_url_base.ReplaceComponents(replacements); |
| |
| SetGlicPagePath("/server-redirect-302"); |
| add_mock_glic_query_param(admin_url.spec()); |
| |
| replacements.SetHostStr("gemini.google.com"); |
| destination_url_ = |
| embedded_https_test_server().GetURL("/echo").ReplaceComponents( |
| replacements); |
| |
| GURL::Replacements pattern_replacements; |
| pattern_replacements.SetPathStr("/echo"); |
| pattern_replacements.SetQueryStr("*"); |
| |
| std::vector<base::test::FeatureRefAndParams> enabled_features = { |
| {features::kGlicDebugWebview, {}}, |
| {features::kGlicCaaGuestError, |
| {{"glic-caa-link-url", destination_url_.spec()}, |
| {"glic-caa-redirect-patterns", |
| admin_url.ReplaceComponents(pattern_replacements).spec()}}}}; |
| |
| redirect_features_.InitWithFeaturesAndParameters(enabled_features, |
| /*disabled_features=*/{}); |
| test::InteractiveGlicTest::SetUp(); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule(admin_hostname_, "127.0.0.1"); |
| host_resolver()->AddRule("gemini.google.com", "127.0.0.1"); |
| |
| test::InteractiveGlicTest::SetUpOnMainThread(); |
| } |
| const GURL& destination_url() { return destination_url_; } |
| |
| protected: |
| auto WaitForTabOpenedTo(int tab, GURL url) { |
| return Steps( |
| PollState(kOpenedTabUrlState, |
| [this, tab]() { |
| auto* const model = browser()->tab_strip_model(); |
| auto* tab_at_index = model->GetTabAtIndex(tab); |
| if (!tab_at_index) { |
| return GURL(); |
| } |
| return tab_at_index->GetContents()->GetVisibleURL(); |
| }), |
| WaitForState(kOpenedTabUrlState, url), |
| StopObservingState(kOpenedTabUrlState)); |
| } |
| |
| base::UserActionTester& user_action_tester() { return user_action_tester_; } |
| |
| private: |
| GURL destination_url_; |
| std::string admin_hostname_; |
| base::UserActionTester user_action_tester_; |
| base::test::ScopedFeatureList redirect_features_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(GlicApiUiRedirectTest, AccessDeniedAdmin) { |
| auto https_server_running = |
| embedded_https_test_server().StartAcceptingConnectionsAndReturnHandle(); |
| |
| RunTestSequence( |
| OpenGlicWindow(GlicWindowMode::kDetached, GlicInstrumentMode::kHostOnly), |
| InAnyContext(WaitForElementVisible( |
| test::kGlicHostElementId, {"#disabledByAdminPanel:not([hidden])"})), |
| CheckTabCount(1), |
| InAnyContext(WaitForElementVisible(test::kGlicHostElementId, |
| {"#disabledByAdminPanel .notice a"})), |
| ClickElement(test::kGlicHostElementId, |
| {"#disabledByAdminPanel .notice a"}) |
| .SetContext(ui::InteractionSequence::ContextMode::kAny) |
| .SetMustRemainVisible(false), |
| InAnyContext(Do([&]() { |
| EXPECT_EQ(user_action_tester().GetActionCount( |
| "Glic.DisabledByAdminPanelLinkClicked"), |
| 1); |
| })), |
| WaitForTabOpenedTo(1, destination_url()), CheckControllerShowing(false)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, GlicApiUiRedirectTest, ::testing::Bool()); |
| |
| IN_PROC_BROWSER_TEST_P(GlicUiConnectedUiTest, AccessDeniedAdminWithoutLink) { |
| RunTestSequence( |
| OpenGlicWindow(GlicWindowMode::kDetached, GlicInstrumentMode::kHostOnly), |
| InAnyContext(Do([&]() { |
| browser()->profile()->GetPrefs()->SetInteger( |
| ::prefs::kGeminiSettings, |
| static_cast<int>(glic::prefs::SettingsPolicyState::kDisabled)); |
| })), |
| |
| InAnyContext(WaitForElementVisible( |
| test::kGlicHostElementId, {"#disabledByAdminPanel:not(.show-disabled-" |
| "by-admin-link) .without-link"})), |
| InAnyContext(EnsureNotVisible(test::kGlicHostElementId, |
| {"#disabledByAdminPanel a"}))); |
| } |
| |
| } // namespace glic |