blob: 72b182778133788d69b00e6ba6cc60b5c6b38b5c [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <optional>
#include <utility>
#include "base/test/bind.h"
#include "chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.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/renderer_context_menu/render_view_context_menu_test_util.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/views/compose/compose_dialog_view.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/interaction/interaction_test_util_browser.h"
#include "chrome/test/interaction/interactive_browser_test.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/compose/core/browser/compose_features.h"
#include "components/compose/core/browser/config.h"
#include "components/optimization_guide/core/mock_optimization_guide_model_executor.h"
#include "components/optimization_guide/core/model_quality/model_quality_log_entry.h"
#include "components/optimization_guide/core/optimization_guide_features.h"
#include "components/optimization_guide/core/optimization_guide_proto_util.h"
#include "components/optimization_guide/proto/features/compose.pb.h"
#include "components/optimization_guide/proto/model_execution.pb.h"
#include "components/optimization_guide/proto/model_quality_service.pb.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/identity_test_utils.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/fenced_frame_test_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/interaction/element_tracker.h"
#include "ui/base/ui_base_features.h"
#include "ui/views/interaction/element_tracker_views.h"
namespace {
using ::optimization_guide::MockSession;
using ::testing::_;
using ::testing::An;
using ::testing::NiceMock;
using ::testing::Return;
using DeepQuery = ::WebContentsInteractionTestUtil::DeepQuery;
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kContentPageTabId);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kComposeWebContents);
DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kElementReadyEvent);
DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kElementChangedEvent);
const char kTestPageDomain[] = "a.test";
const char kTestPageUrl[] = "/compose/compose_happy_path.html";
const DeepQuery kFirstRunOkButton = {"compose-app", "#firstRunOkButton"};
const DeepQuery kSubmitButton = {"compose-app", "#submitButton"};
const DeepQuery kAcceptButton = {"compose-app", "#acceptButton"};
const DeepQuery kComposeTextArea = {"compose-app", "compose-textarea"};
const DeepQuery kTextarea = {"#elem1"};
} // namespace
// TODO(b/319449485): Enable this test on Mac. In development it timed out for
// unknown reasons (possibly related to the Mac handling of context menus.
#if BUILDFLAG(IS_MAC)
#define MAYBE_ComposeInteractiveUiTest DISABLED_ComposeInteractiveUiTest
#else
#define MAYBE_ComposeInteractiveUiTest ComposeInteractiveUiTest
#endif
class MAYBE_ComposeInteractiveUiTest : public InteractiveBrowserTest {
public:
MAYBE_ComposeInteractiveUiTest() {
feature_list_.InitWithFeatures(
{compose::features::kEnableCompose,
optimization_guide::features::kOptimizationGuideModelExecution},
{});
subscription_ =
BrowserContextDependencyManager::GetInstance()
->RegisterCreateServicesCallbackForTesting(
base::BindRepeating(&MAYBE_ComposeInteractiveUiTest::
OnWillCreateBrowserContextServices));
}
~MAYBE_ComposeInteractiveUiTest() override = default;
/////////////////////////////////////////
// Compose interactive UI test step helpers.
InteractiveTestApi::StepBuilder PressJsButton(
const ui::ElementIdentifier web_contents_id,
const DeepQuery& button_query) {
// This can close/navigate the current page, so don't wait for success.
return ExecuteJsAt(web_contents_id, button_query, "(btn) => btn.click()",
ExecuteJsMode::kFireAndForget);
}
InteractiveTestApi::MultiStep WaitForElementToLoad(
const DeepQuery& element_query) {
StateChange element_loaded;
element_loaded.event = kElementReadyEvent;
element_loaded.type = StateChange::Type::kExists;
element_loaded.where = element_query;
return WaitForStateChange(kContentPageTabId, std::move(element_loaded));
}
InteractiveTestApi::MultiStep WaitForElementValueToIncludeCucumbers(
const DeepQuery& element_query) {
StateChange element_changed;
element_changed.event = kElementChangedEvent;
element_changed.type = StateChange::Type::kExistsAndConditionTrue;
element_changed.where = element_query;
element_changed.test_function =
R"((el) => (el.value.includes("Cucumbers")))";
return WaitForStateChange(kContentPageTabId, std::move(element_changed));
}
InteractiveTestApi::MultiStep OpenCompose() {
return Steps(
WaitForElementToLoad(kTextarea),
MoveMouseTo(kContentPageTabId, kTextarea),
MayInvolveNativeContextMenu(
ClickMouse(ui_controls::RIGHT),
SelectMenuItem(RenderViewContextMenu::kComposeMenuItem)),
WaitForShow(ComposeDialogView::kComposeDialogId),
InstrumentNonTabWebView(kComposeWebContents, kComposeWebviewElementId));
}
InteractiveTestApi::MultiStep AcceptFRE() {
return Steps(PressJsButton(kComposeWebContents, kFirstRunOkButton));
}
InteractiveTestApi::MultiStep RequestCompose() {
return Steps(ExecuteJsAt(kComposeWebContents, kComposeTextArea,
"(ct) => { ct.value = 'Abra Cadabra 1,2,3'; }"),
PressJsButton(kComposeWebContents, kSubmitButton));
}
InteractiveTestApi::MultiStep AcceptComposeResult() {
return Steps(PressJsButton(kComposeWebContents, kAcceptButton));
}
InteractiveTestApi::StepBuilder MakePrimaryAccountAvailable() {
return Do([this]() {
identity_test_environment_adaptor_->identity_test_env()
->MakePrimaryAccountAvailable("[email protected]",
signin::ConsentLevel::kSync);
});
}
/////////////////////////////////////////
// Setup tasks
void SetUpOnMainThread() override {
InteractiveBrowserTest::SetUpOnMainThread();
compose::ResetConfigForTesting();
identity_test_environment_adaptor_ =
std::make_unique<IdentityTestEnvironmentProfileAdaptor>(
browser()->profile());
host_resolver()->AddRule("*", "127.0.0.1");
// Add content/test/data for cross_site_iframe_factory.html
https_server()->ServeFilesFromSourceDirectory("chrome/test/data");
https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
content::SetupCrossSiteRedirector(https_server());
net::test_server::RegisterDefaultHandlers(https_server());
ASSERT_TRUE(https_server()->Start());
SetUpOptimizationGuide();
SetUpAccount();
}
void TearDownOnMainThread() override {
base::RunLoop().RunUntilIdle();
mock_optimization_guide_keyed_service_ = nullptr;
testing::Mock::VerifyAndClear(&session());
InteractiveBrowserTest::TearDownOnMainThread();
}
// Sets up the guide to not make a decision about Compose per-page, and
// to return a response of "Cucumbers" whenever ExecuteModel is called.
void SetUpOptimizationGuide() {
mock_optimization_guide_keyed_service_ =
static_cast<testing::NiceMock<MockOptimizationGuideKeyedService>*>(
OptimizationGuideKeyedServiceFactory::GetForProfile(
browser()->profile()));
ASSERT_TRUE(mock_optimization_guide_keyed_service_);
ON_CALL(
*mock_optimization_guide_keyed_service_,
CanApplyOptimization(
_, _, An<optimization_guide::OptimizationGuideDecisionCallback>()))
.WillByDefault(testing::Invoke(
[](const GURL&, optimization_guide::proto::OptimizationType type,
optimization_guide::OptimizationGuideDecisionCallback callback) {
std::move(callback).Run(
optimization_guide::OptimizationGuideDecision::kTrue,
optimization_guide::OptimizationMetadata());
}));
ON_CALL(*mock_optimization_guide_keyed_service_,
CanApplyOptimization(
_, _, An<optimization_guide::OptimizationMetadata*>()))
.WillByDefault(
Return(optimization_guide::OptimizationGuideDecision::kTrue));
ON_CALL(*mock_optimization_guide_keyed_service_,
ShouldFeatureBeCurrentlyEnabledForUser)
.WillByDefault(Return(true));
ON_CALL(*mock_optimization_guide_keyed_service_, StartSession(_, _))
.WillByDefault([&] {
return std::make_unique<NiceMock<MockSession>>(&session());
});
ON_CALL(session(), ExecuteModel(_, _))
.WillByDefault(testing::WithArg<1>(testing::Invoke(
[&](optimization_guide::
OptimizationGuideModelExecutionResultStreamingCallback
callback) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
std::move(callback),
OptimizationGuideStreamingResult(
ComposeResponse(true, "Cucumbers"), true, false)));
})));
}
void SetUpAccount() {
// Turn on MSBB.
PrefService* prefs = browser()->profile()->GetPrefs();
prefs->SetBoolean(
unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, true);
}
base::test::ScopedFeatureList* feature_list() { return &feature_list_; }
net::EmbeddedTestServer* https_server() { return &https_server_; }
content::test::FencedFrameTestHelper& fenced_frame_test_helper() {
return fenced_frame_test_helper_;
}
optimization_guide::MockSession& session() { return session_; }
private:
static void OnWillCreateBrowserContextServices(
content::BrowserContext* context) {
IdentityTestEnvironmentProfileAdaptor::
SetIdentityTestEnvironmentFactoriesOnBrowserContext(context);
OptimizationGuideKeyedServiceFactory::GetInstance()->SetTestingFactory(
context, base::BindRepeating([](content::BrowserContext* context)
-> std::unique_ptr<KeyedService> {
return std::make_unique<
testing::NiceMock<MockOptimizationGuideKeyedService>>();
}));
}
optimization_guide::StreamingResponse OptimizationGuideResponse(
const optimization_guide::proto::ComposeResponse compose_response,
bool is_complete = true) {
return optimization_guide::StreamingResponse{
.response = optimization_guide::AnyWrapProto(compose_response),
.is_complete = is_complete,
};
}
optimization_guide::OptimizationGuideModelStreamingExecutionResult
OptimizationGuideStreamingResult(
const optimization_guide::proto::ComposeResponse compose_response,
bool is_complete = true,
bool provided_by_on_device = false) {
return optimization_guide::OptimizationGuideModelStreamingExecutionResult(
base::ok(OptimizationGuideResponse(compose_response, is_complete)),
provided_by_on_device);
}
optimization_guide::proto::ComposeResponse ComposeResponse(
bool ok,
std::string output) {
optimization_guide::proto::ComposeResponse response;
response.set_output(ok ? output : "");
return response;
}
content::test::FencedFrameTestHelper fenced_frame_test_helper_;
base::test::ScopedFeatureList feature_list_;
net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
base::CallbackListSubscription subscription_;
raw_ptr<testing::NiceMock<MockOptimizationGuideKeyedService>>
mock_optimization_guide_keyed_service_;
std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
identity_test_environment_adaptor_;
testing::NiceMock<optimization_guide::MockSession> session_;
};
// Flaky on all platforms: https://crbug.com/1517430
IN_PROC_BROWSER_TEST_F(MAYBE_ComposeInteractiveUiTest,
DISABLED_OpenAndCloseCompose) {
RunTestSequence(
MakePrimaryAccountAvailable(), InstrumentTab(kContentPageTabId),
NavigateWebContents(
kContentPageTabId,
https_server()->GetURL(kTestPageDomain, kTestPageUrl)),
WaitForWebContentsReady(
kContentPageTabId,
https_server()->GetURL(kTestPageDomain, kTestPageUrl)),
OpenCompose(), AcceptFRE(), RequestCompose(), AcceptComposeResult(),
WaitForElementValueToIncludeCucumbers(kTextarea));
}