blob: 1f865ea9d1786ad7e47d276b88abccd35f4e51b9 [file] [log] [blame]
// Copyright 2022 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/ui/autofill/chrome_autofill_client.h"
#include <optional>
#include <utility>
#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/test/scoped_feature_list.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "chrome/browser/autofill/mock_autofill_agent.h"
#include "chrome/browser/autofill/personal_data_manager_factory.h"
#include "chrome/browser/autofill/ui/ui_util.h"
#include "chrome/browser/plus_addresses/plus_address_service_factory.h"
#include "chrome/browser/ssl/chrome_security_state_tab_helper.h"
#include "chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/hats/hats_service.h"
#include "chrome/browser/ui/user_education/browser_user_education_interface.h"
#include "chrome/browser/user_education/user_education_service.h"
#include "chrome/browser/user_education/user_education_service_factory.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/user_education/mock_browser_user_education_interface.h"
#include "components/autofill/content/browser/autofill_test_utils.h"
#include "components/autofill/content/browser/test_autofill_client_injector.h"
#include "components/autofill/content/browser/test_autofill_driver_injector.h"
#include "components/autofill/content/browser/test_autofill_manager_injector.h"
#include "components/autofill/content/browser/test_content_autofill_driver.h"
#include "components/autofill/core/browser/data_manager/test_personal_data_manager.h"
#include "components/autofill/core/browser/data_model/addresses/autofill_profile.h"
#include "components/autofill/core/browser/data_model/addresses/autofill_profile_test_api.h"
#include "components/autofill/core/browser/data_model/autofill_ai/entity_type.h"
#include "components/autofill/core/browser/data_model/autofill_ai/entity_type_names.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/foundations/test_autofill_manager_waiter.h"
#include "components/autofill/core/browser/foundations/test_browser_autofill_manager.h"
#include "components/autofill/core/browser/integrators/fast_checkout/mock_fast_checkout_client.h"
#include "components/autofill/core/browser/integrators/password_form_classification.h"
#include "components/autofill/core/browser/payments/payments_autofill_client.h"
#include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
#include "components/autofill/core/browser/ui/mock_autofill_suggestion_delegate.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/autofill/core/common/autofill_test_utils.h"
#include "components/autofill/core/common/form_field_data.h"
#include "components/autofill/core/common/form_interactions_flow.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/plus_addresses/core/browser/fake_plus_address_service.h"
#include "components/plus_addresses/core/browser/plus_address_hats_utils.h"
#include "components/plus_addresses/core/common/features.h"
#include "components/prefs/pref_service.h"
#include "components/strings/grit/components_strings.h"
#include "components/unified_consent/pref_names.h"
#include "components/user_education/common/feature_promo/feature_promo_result.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/navigation_simulator.h"
#include "mojo/public/cpp/bindings/associated_receiver_set.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_ANDROID)
#include "chrome/browser/fast_checkout/fast_checkout_client_impl.h"
#include "chrome/browser/ui/android/autofill/autofill_accessibility_utils.h"
#include "chrome/browser/ui/android/autofill/autofill_cvc_save_message_delegate.h"
#include "chrome/browser/ui/android/autofill/autofill_save_card_bottom_sheet_bridge.h"
#include "chrome/browser/ui/android/autofill/autofill_save_card_delegate_android.h"
#include "components/autofill/core/browser/payments/autofill_save_card_ui_info.h"
#else
#include "chrome/browser/ui/autofill/autofill_field_promo_controller.h"
#include "chrome/browser/ui/autofill/payments/save_card_bubble_controller_impl.h"
#include "chrome/browser/ui/hats/hats_service_factory.h"
#include "chrome/browser/ui/hats/mock_hats_service.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/test_browser_window.h"
#endif
namespace autofill {
namespace {
using ::autofill::test::CreateFormDataForRenderFrameHost;
using ::autofill::test::CreateTestFormField;
using ::testing::_;
using ::testing::A;
using ::testing::AllOf;
using ::testing::Eq;
using ::testing::Field;
using ::testing::Ge;
using ::testing::InSequence;
using ::testing::Le;
using ::testing::Pair;
using ::testing::Ref;
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::UnorderedElementsAre;
#if !BUILDFLAG(IS_ANDROID)
class MockSaveCardBubbleController : public SaveCardBubbleControllerImpl {
public:
explicit MockSaveCardBubbleController(content::WebContents* web_contents)
: SaveCardBubbleControllerImpl(web_contents) {}
~MockSaveCardBubbleController() override = default;
MOCK_METHOD(
void,
ShowConfirmationBubbleView,
(bool,
std::optional<
payments::PaymentsAutofillClient::OnConfirmationClosedCallback>),
(override));
MOCK_METHOD(void, HideSaveCardBubble, (), (override));
};
#endif
#if BUILDFLAG(IS_ANDROID)
class MockAutofillAccessibilityHelper : public AutofillAccessibilityHelper {
public:
MockAutofillAccessibilityHelper() = default;
~MockAutofillAccessibilityHelper() override = default;
MOCK_METHOD(void,
AnnounceTextForA11y,
(const std::u16string& message),
(override));
};
#endif
#if !BUILDFLAG(IS_ANDROID)
class MockAutofillFieldPromoController : public AutofillFieldPromoController {
public:
~MockAutofillFieldPromoController() override = default;
MOCK_METHOD(void, Show, (const gfx::RectF&), (override));
MOCK_METHOD(void, Hide, (), (override));
MOCK_METHOD(bool, IsMaybeShowing, (), (const override));
MOCK_METHOD(const base::Feature&, GetFeaturePromo, (), (const override));
};
#endif // !BUILDFLAG(IS_ANDROID)
// This test class is needed to make the constructor public.
class TestChromeAutofillClient : public ChromeAutofillClient {
public:
explicit TestChromeAutofillClient(content::WebContents* web_contents)
: ChromeAutofillClient(web_contents) {}
~TestChromeAutofillClient() override = default;
};
class ChromeAutofillClientTest : public ChromeRenderViewHostTestHarness {
public:
ChromeAutofillClientTest()
: ChromeRenderViewHostTestHarness(
base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
// Enable MSBB by default. If MSBB has been explicitly turned off, Fast
// Checkout is not supported.
profile()->GetPrefs()->SetBoolean(
unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, true);
// Creates the AutofillDriver and AutofillManager.
NavigateAndCommit(GURL("about:blank"));
#if !BUILDFLAG(IS_ANDROID)
ChromeSecurityStateTabHelper::CreateForWebContents(web_contents());
auto save_card_bubble_controller =
std::make_unique<MockSaveCardBubbleController>(web_contents());
const auto* user_data_key = save_card_bubble_controller->UserDataKey();
web_contents()->SetUserData(user_data_key,
std::move(save_card_bubble_controller));
#endif
}
#if !BUILDFLAG(IS_ANDROID)
void SetUpIphForTesting(const base::Feature& feature_promo) {
auto autofill_field_promo_controller =
std::make_unique<MockAutofillFieldPromoController>();
autofill_field_promo_controller_ = autofill_field_promo_controller.get();
ON_CALL(*autofill_field_promo_controller_, IsMaybeShowing)
.WillByDefault(Return(false));
ON_CALL(*autofill_field_promo_controller_, GetFeaturePromo)
.WillByDefault(ReturnRef(feature_promo));
client()->SetAutofillFieldPromoTesting(
std::move(autofill_field_promo_controller));
}
#endif // !BUILDFLAG(IS_ANDROID)
void TearDown() override {
// Avoid that the raw pointer becomes dangling.
#if !BUILDFLAG(IS_ANDROID)
autofill_field_promo_controller_ = nullptr;
#endif // !BUILDFLAG(IS_ANDROID)
ChromeRenderViewHostTestHarness::TearDown();
}
protected:
TestChromeAutofillClient* client() {
return test_autofill_client_injector_[web_contents()];
}
ContentAutofillDriver* driver(content::RenderFrameHost* rfh) {
return ContentAutofillDriver::GetForRenderFrameHost(rfh);
}
#if !BUILDFLAG(IS_ANDROID)
MockAutofillFieldPromoController* autofill_field_promo_controller() {
return autofill_field_promo_controller_;
}
#endif // !BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(IS_ANDROID)
// Helper function to set up mock accessibility helper for Android tests.
MockAutofillAccessibilityHelper* SetUpMockAccessibilityHelper() {
mock_accessibility_helper_ =
std::make_unique<MockAutofillAccessibilityHelper>();
MockAutofillAccessibilityHelper* mock_ptr =
mock_accessibility_helper_.get();
AutofillAccessibilityHelper::SetInstanceForTesting(mock_ptr);
return mock_ptr;
}
void TearDownMockAccessibilityHelper() {
AutofillAccessibilityHelper::SetInstanceForTesting(nullptr);
mock_accessibility_helper_.reset();
}
#endif
#if !BUILDFLAG(IS_ANDROID)
MockSaveCardBubbleController& save_card_bubble_controller() {
return static_cast<MockSaveCardBubbleController&>(
*SaveCardBubbleControllerImpl::FromWebContents(web_contents()));
}
#endif
private:
TestingProfile::TestingFactories GetTestingFactories() const override {
return {TestingProfile::TestingFactory{
autofill::PersonalDataManagerFactory::GetInstance(),
base::BindRepeating(&CreateTestPersonalDataManager)},
TestingProfile::TestingFactory{
PlusAddressServiceFactory::GetInstance(),
base::BindRepeating(&BuildFakePlusAddressService)}};
}
static std::unique_ptr<KeyedService> CreateTestPersonalDataManager(
content::BrowserContext* context) {
auto pdm = std::make_unique<TestPersonalDataManager>();
pdm->test_address_data_manager().SetAutofillProfileEnabled(true);
pdm->test_payments_data_manager().SetAutofillPaymentMethodsEnabled(true);
pdm->test_payments_data_manager().SetAutofillWalletImportEnabled(false);
return pdm;
}
static std::unique_ptr<KeyedService> BuildFakePlusAddressService(
content::BrowserContext* context) {
return std::make_unique<plus_addresses::FakePlusAddressService>();
}
autofill::test::AutofillUnitTestEnvironment autofill_environment_{
{.disable_server_communication = true}};
base::test::ScopedFeatureList scoped_feature_list_{
plus_addresses::features::kPlusAddressesEnabled};
#if !BUILDFLAG(IS_ANDROID)
raw_ptr<MockAutofillFieldPromoController> autofill_field_promo_controller_;
#endif // !BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(IS_ANDROID)
std::unique_ptr<MockAutofillAccessibilityHelper> mock_accessibility_helper_;
#endif
TestAutofillClientInjector<TestChromeAutofillClient>
test_autofill_client_injector_;
base::OnceCallback<void()> setup_flags_;
};
// Tests that `ClassifyAsPasswordForm()` correctly recognizes a login form on a
// single frame.
TEST_F(ChromeAutofillClientTest, ClassifiesLoginFormOnMainFrame) {
constexpr char kUrl[] = "https://www.foo.com/login.html";
NavigateAndCommit(GURL(kUrl));
ContentAutofillDriver* autofill_driver = driver(main_rfh());
ASSERT_TRUE(autofill_driver);
FormData form = CreateFormDataForRenderFrameHost(
*main_rfh(), {CreateTestFormField("Username", "username", "",
FormControlType::kInputText),
CreateTestFormField("Password", "password", "",
FormControlType::kInputPassword)});
{
TestAutofillManagerWaiter waiter(autofill_driver->GetAutofillManager(),
{AutofillManagerEvent::kFormsSeen});
autofill_driver->renderer_events().FormsSeen(/*updated_forms=*/{form},
/*removed_forms=*/{});
ASSERT_TRUE(waiter.Wait(/*num_expected_relevant_events=*/1));
}
const auto expected = PasswordFormClassification{
.type = PasswordFormClassification::Type::kLoginForm,
.username_field = form.fields()[0].global_id(),
.password_field = form.fields()[1].global_id()};
EXPECT_EQ(client()->ClassifyAsPasswordForm(
autofill_driver->GetAutofillManager(), form.global_id(),
form.fields()[0].global_id()),
expected);
}
// Tests that `ClassifyAsPasswordForm()` correctly recognizes a login form on
// a child frame.
TEST_F(ChromeAutofillClientTest, ClassifiesLoginFormOnChildFrame) {
constexpr char kUrl1[] = "https://www.foo.com/login.html";
constexpr char kUrl2[] = "https://www.foo.com/otp.html";
NavigateAndCommit(GURL(kUrl1));
content::RenderFrameHost* child_rfh =
content::RenderFrameHostTester::For(main_rfh())
->AppendChild(std::string("child"));
child_rfh = content::NavigationSimulator::NavigateAndCommitFromDocument(
GURL(kUrl2), child_rfh);
ContentAutofillClient* autofill_client =
ContentAutofillClient::FromWebContents(web_contents());
ASSERT_TRUE(autofill_client);
ContentAutofillDriver* main_driver = driver(main_rfh());
ContentAutofillDriver* child_driver = driver(child_rfh);
ASSERT_TRUE(main_driver);
ASSERT_TRUE(child_driver);
FormData main_form = CreateFormDataForRenderFrameHost(
*main_rfh(), {CreateTestFormField("Search", "search", "",
FormControlType::kInputText)});
FormData child_form = CreateFormDataForRenderFrameHost(
*child_rfh, {CreateTestFormField("Username", "username", "",
FormControlType::kInputText),
CreateTestFormField("Password", "password", "",
FormControlType::kInputPassword)});
// Ensure that the child frame is picked up as a child frame of `main_form`.
{
autofill::FrameTokenWithPredecessor child_frame_information;
child_frame_information.token = child_form.host_frame();
main_form.set_child_frames({child_frame_information});
}
{
autofill::TestAutofillManagerWaiter waiter(
main_driver->GetAutofillManager(),
{autofill::AutofillManagerEvent::kFormsSeen});
main_driver->renderer_events().FormsSeen(/*updated_forms=*/{main_form},
/*removed_forms=*/{});
child_driver->renderer_events().FormsSeen(/*updated_forms=*/{child_form},
/*removed_forms=*/{});
ASSERT_TRUE(waiter.Wait(/*num_expected_relevant_events=*/2));
}
// The form fields in the main frame do not form a valid password form.
EXPECT_EQ(client()->ClassifyAsPasswordForm(main_driver->GetAutofillManager(),
main_form.global_id(),
main_form.fields()[0].global_id()),
PasswordFormClassification());
// The form fields in the child frame form a login form.
const auto expected = PasswordFormClassification{
.type = PasswordFormClassification::Type::kLoginForm,
.username_field = child_form.fields()[0].global_id(),
.password_field = child_form.fields()[1].global_id()};
EXPECT_EQ(client()->ClassifyAsPasswordForm(
main_driver->GetAutofillManager(), main_form.global_id(),
child_form.fields()[0].global_id()),
expected);
}
TEST_F(ChromeAutofillClientTest, GetFormInteractionsFlowId_BelowMaxFlowTime) {
base::TimeDelta below_max_flow_time = base::Minutes(10);
FormInteractionsFlowId first_interaction_flow_id =
client()->GetCurrentFormInteractionsFlowId();
task_environment()->FastForwardBy(below_max_flow_time);
EXPECT_EQ(first_interaction_flow_id,
client()->GetCurrentFormInteractionsFlowId());
}
TEST_F(ChromeAutofillClientTest, GetFormInteractionsFlowId_AboveMaxFlowTime) {
base::TimeDelta above_max_flow_time = base::Minutes(21);
FormInteractionsFlowId first_interaction_flow_id =
client()->GetCurrentFormInteractionsFlowId();
task_environment()->FastForwardBy(above_max_flow_time);
EXPECT_NE(first_interaction_flow_id,
client()->GetCurrentFormInteractionsFlowId());
}
TEST_F(ChromeAutofillClientTest, GetFormInteractionsFlowId_AdvancedTwice) {
base::TimeDelta above_half_max_flow_time = base::Minutes(15);
FormInteractionsFlowId first_interaction_flow_id =
client()->GetCurrentFormInteractionsFlowId();
task_environment()->FastForwardBy(above_half_max_flow_time);
FormInteractionsFlowId second_interaction_flow_id =
client()->GetCurrentFormInteractionsFlowId();
task_environment()->FastForwardBy(above_half_max_flow_time);
EXPECT_EQ(first_interaction_flow_id, second_interaction_flow_id);
EXPECT_NE(first_interaction_flow_id,
client()->GetCurrentFormInteractionsFlowId());
}
// Ensure that, by default, the plus address service is not available.
// The positive case (feature enabled) will be tested in plus_addresses browser
// tests; this test is intended to ensure the default state does not behave
// unexpectedly.
TEST_F(ChromeAutofillClientTest,
PlusAddressDefaultFeatureStateMeansNullPlusAddressService) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndDisableFeature(
plus_addresses::features::kPlusAddressesEnabled);
PlusAddressServiceFactory::GetForBrowserContext(
web_contents()->GetBrowserContext());
EXPECT_EQ(client()->GetPlusAddressDelegate(), nullptr);
}
#if !BUILDFLAG(IS_ANDROID)
// Test the scenario when the plus address survey delay is not configured. The
// random delay of the survey should be between the 10s and 60s.
TEST_F(ChromeAutofillClientTest,
TriggerPlusAddressUserPerceptionSurvey_DelayNotConfigured) {
base::test::ScopedFeatureList scoped_feature_list_{
features::kPlusAddressAcceptedFirstTimeCreateSurvey};
MockHatsService* mock_hats_service = static_cast<MockHatsService*>(
HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse(
profile(), base::BindRepeating(&BuildMockHatsService)));
EXPECT_CALL(*mock_hats_service, CanShowAnySurvey)
.WillRepeatedly(Return(true));
EXPECT_CALL(
*mock_hats_service,
LaunchDelayedSurveyForWebContents(
kHatsSurveyTriggerPlusAddressAcceptedFirstTimeCreate, _,
AllOf(Ge(10000), Le(60000)), _,
UnorderedElementsAre(
Pair(plus_addresses::hats::kPlusAddressesCount, std::string("0")),
Pair(plus_addresses::hats::kFirstPlusAddressCreationTime,
std::string("-1")),
Pair(plus_addresses::hats::kLastPlusAddressFillingTime,
std::string("-1"))),
HatsService::NavigationBehavior::ALLOW_ANY, _, _, _, _));
client()->TriggerPlusAddressUserPerceptionSurvey(
plus_addresses::hats::SurveyType::kAcceptedFirstTimeCreate);
}
// Test that the hats service is called with the expected params for different
// surveys.
TEST_F(ChromeAutofillClientTest, TriggerPlusAddressUserPerceptionSurvey) {
base::test::ScopedFeatureList scoped_feature_list_;
scoped_feature_list_.InitWithFeaturesAndParameters(
/*enabled_features=*/{{features::
kPlusAddressAcceptedFirstTimeCreateSurvey,
{{plus_addresses::hats::kMinDelayMs, "10"},
{plus_addresses::hats::kMaxDelayMs, "60"}}}},
/*disabled_features=*/{});
MockHatsService* mock_hats_service = static_cast<MockHatsService*>(
HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse(
profile(), base::BindRepeating(&BuildMockHatsService)));
EXPECT_CALL(*mock_hats_service, CanShowAnySurvey)
.WillRepeatedly(Return(true));
EXPECT_CALL(
*mock_hats_service,
LaunchDelayedSurveyForWebContents(
kHatsSurveyTriggerPlusAddressAcceptedFirstTimeCreate, _,
AllOf(Ge(10), Le(60)), _,
UnorderedElementsAre(
Pair(plus_addresses::hats::kPlusAddressesCount, std::string("0")),
Pair(plus_addresses::hats::kFirstPlusAddressCreationTime,
std::string("-1")),
Pair(plus_addresses::hats::kLastPlusAddressFillingTime,
std::string("-1"))),
HatsService::NavigationBehavior::ALLOW_ANY, _, _, _, _));
client()->TriggerPlusAddressUserPerceptionSurvey(
plus_addresses::hats::SurveyType::kAcceptedFirstTimeCreate);
}
// Test that the hats service is called with the expected params for different
// surveys. Note that Surveys are only launched on Desktop.
TEST_F(ChromeAutofillClientTest, TriggerUserPerceptionOfAutofillAddressSurvey) {
MockHatsService* mock_hats_service = static_cast<MockHatsService*>(
HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse(
profile(), base::BindRepeating(&BuildMockHatsService)));
EXPECT_CALL(*mock_hats_service, CanShowAnySurvey)
.WillRepeatedly(Return(true));
const SurveyStringData field_filling_stats_data;
EXPECT_CALL(*mock_hats_service,
LaunchDelayedSurveyForWebContents(
kHatsSurveyTriggerAutofillAddressUserPerception, _, _, _,
Ref(field_filling_stats_data), _, _, _, _, _));
client()->TriggerUserPerceptionOfAutofillSurvey(FillingProduct::kAddress,
field_filling_stats_data);
}
// Test that the Autofill AI filling journey survey calls the hats service with
// the expected params.
TEST_F(ChromeAutofillClientTest,
TriggerUserAutofillAiFillingJourneySurvey_Vehicle_SuggestionAccepted) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(features::kAutofillAiFillingSurvey);
MockHatsService* mock_hats_service = static_cast<MockHatsService*>(
HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse(
profile(), base::BindRepeating(&BuildMockHatsService)));
EXPECT_CALL(*mock_hats_service, CanShowAnySurvey)
.WillRepeatedly(Return(true));
EXPECT_CALL(
*mock_hats_service,
LaunchDelayedSurveyForWebContents(
kHatsSurveyTriggerAutofillAiFilling, _, _,
Eq(SurveyBitsData({{"User accepted suggestion", true}})),
Eq(SurveyStringData({{"Entity type", "Vehicle"},
{"Saved entities", "Vehicle"},
{"Triggering field types", "NAME_FULL"}})),
_, _, _, _, _));
client()->TriggerAutofillAiFillingJourneySurvey(
/*suggestion_accepted=*/true, EntityType(EntityTypeName::kVehicle),
base::flat_set<EntityTypeName>({EntityTypeName::kVehicle}), {NAME_FULL});
}
// Test that some entities (such as passports) does not trigger AutofillAi
// filling surveys.
TEST_F(ChromeAutofillClientTest,
TriggerUserAutofillAiFillingJourneySurvey_Passport_SurveyNotTriggered) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(features::kAutofillAiFillingSurvey);
MockHatsService* mock_hats_service = static_cast<MockHatsService*>(
HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse(
profile(), base::BindRepeating(&BuildMockHatsService)));
EXPECT_CALL(*mock_hats_service, CanShowAnySurvey)
.WillRepeatedly(Return(true));
EXPECT_CALL(*mock_hats_service, LaunchDelayedSurveyForWebContents).Times(0);
client()->TriggerAutofillAiFillingJourneySurvey(
/*suggestion_accepted=*/true, EntityType(EntityTypeName::kPassport),
base::flat_set<EntityTypeName>({EntityTypeName::kPassport}),
{PASSPORT_NUMBER});
}
TEST_F(
ChromeAutofillClientTest,
TriggerUserAutofillAiFillingJourneySurvey_FlightReservation_SuggestionDeclined) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(features::kAutofillAiFillingSurvey);
MockHatsService* mock_hats_service = static_cast<MockHatsService*>(
HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse(
profile(), base::BindRepeating(&BuildMockHatsService)));
EXPECT_CALL(*mock_hats_service, CanShowAnySurvey)
.WillRepeatedly(Return(true));
EXPECT_CALL(
*mock_hats_service,
LaunchDelayedSurveyForWebContents(
kHatsSurveyTriggerAutofillAiFilling, _, _,
Eq(SurveyBitsData({{"User accepted suggestion", false}})),
Eq(SurveyStringData(
{{"Entity type",
std::string(EntityType(EntityTypeName::kFlightReservation)
.name_as_string())},
{"Triggering field types", "FLIGHT_RESERVATION_FLIGHT_NUMBER"},
{"Saved entities", "Passport,Flight Reservation"}})),
_, _, _, _, _));
client()->TriggerAutofillAiFillingJourneySurvey(
/*suggestion_accepted=*/false,
EntityType(EntityTypeName::kFlightReservation),
base::flat_set<EntityTypeName>(
{EntityTypeName::kPassport, EntityTypeName::kFlightReservation}),
FieldTypeSet({FLIGHT_RESERVATION_FLIGHT_NUMBER}));
}
// Test that the Autofill AI save prompt survey calls the hats service with
// the expected params.
TEST_F(ChromeAutofillClientTest,
TriggerUserAutofillAiSavePromptSurvey_Accepted) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeaturesAndParameters(
/*enabled_features=*/{{features::kAutofillAiSavePromptSurvey,
{{"autofill_ai_walletable_entity_save_prompt_"
"survey_accepted_trigger_id",
"12345"}}}},
/*disabled_features=*/{});
MockHatsService* mock_hats_service = static_cast<MockHatsService*>(
HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse(
profile(), base::BindRepeating(&BuildMockHatsService)));
EXPECT_CALL(*mock_hats_service, CanShowAnySurvey)
.WillRepeatedly(Return(true));
EXPECT_CALL(*mock_hats_service,
LaunchDelayedSurveyForWebContents(
kHatsSurveyTriggerAutofillAiSavePrompt, _, _, _,
Eq(SurveyStringData({{"Entity type", "Vehicle"},
{"Saved entities", "Vehicle"}})),
_, _, _, Eq("12345"), _));
client()->TriggerAutofillAiSavePromptSurvey(
/*prompt_accepted=*/true, EntityType(EntityTypeName::kVehicle),
base::flat_set<EntityTypeName>({EntityTypeName::kVehicle}));
}
TEST_F(ChromeAutofillClientTest,
TriggerUserAutofillAiSavePromptSurvey_Declined) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeaturesAndParameters(
/*enabled_features=*/{{features::kAutofillAiSavePromptSurvey,
{{"autofill_ai_walletable_entity_save_prompt_"
"survey_declined_trigger_id",
"12345"}}}},
/*disabled_features=*/{});
MockHatsService* mock_hats_service = static_cast<MockHatsService*>(
HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse(
profile(), base::BindRepeating(&BuildMockHatsService)));
EXPECT_CALL(*mock_hats_service, CanShowAnySurvey)
.WillRepeatedly(Return(true));
EXPECT_CALL(*mock_hats_service,
LaunchDelayedSurveyForWebContents(
kHatsSurveyTriggerAutofillAiSavePrompt, _, _, _,
Eq(SurveyStringData({{"Entity type", "Vehicle"},
{"Saved entities", "Vehicle"}})),
_, _, _, Eq("12345"), _));
client()->TriggerAutofillAiSavePromptSurvey(
/*prompt_accepted=*/false, EntityType(EntityTypeName::kVehicle),
base::flat_set<EntityTypeName>({EntityTypeName::kVehicle}));
}
TEST_F(ChromeAutofillClientTest,
TriggerUserPerceptionOfAutofillCreditCardSurvey) {
MockHatsService* mock_hats_service = static_cast<MockHatsService*>(
HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse(
profile(), base::BindRepeating(&BuildMockHatsService)));
EXPECT_CALL(*mock_hats_service, CanShowAnySurvey)
.WillRepeatedly(Return(true));
const SurveyStringData field_filling_stats_data;
EXPECT_CALL(*mock_hats_service,
LaunchDelayedSurveyForWebContents(
kHatsSurveyTriggerAutofillCreditCardUserPerception, _, _, _,
Ref(field_filling_stats_data), _, _, _, _, _));
client()->TriggerUserPerceptionOfAutofillSurvey(FillingProduct::kCreditCard,
field_filling_stats_data);
}
TEST_F(ChromeAutofillClientTest,
CreditCardUploadCompleted_ShowConfirmationBubbleView_CardSaved) {
EXPECT_CALL(save_card_bubble_controller(),
ShowConfirmationBubbleView(
true, A<std::optional<payments::PaymentsAutofillClient::
OnConfirmationClosedCallback>>()));
client()->GetPaymentsAutofillClient()->CreditCardUploadCompleted(
payments::PaymentsAutofillClient::PaymentsRpcResult::kSuccess,
/*on_confirmation_closed_callback=*/std::nullopt);
}
TEST_F(ChromeAutofillClientTest,
CreditCardUploadCompleted_ShowConfirmationBubbleView_CardNotSaved) {
EXPECT_CALL(save_card_bubble_controller(),
ShowConfirmationBubbleView(
false, A<std::optional<payments::PaymentsAutofillClient::
OnConfirmationClosedCallback>>()));
client()->GetPaymentsAutofillClient()->CreditCardUploadCompleted(
payments::PaymentsAutofillClient::PaymentsRpcResult::kPermanentFailure,
/*on_confirmation_closed_callback=*/std::nullopt);
}
// Test that on getting client-side timeout, save card dialog is dismissed and
// confirmation dialog is not shown.
TEST_F(ChromeAutofillClientTest,
CreditCardUploadCompleted_NoConfirmationBubbleView_OnRequestTimeout) {
EXPECT_CALL(save_card_bubble_controller(), HideSaveCardBubble());
EXPECT_CALL(save_card_bubble_controller(),
ShowConfirmationBubbleView(
false, A<std::optional<payments::PaymentsAutofillClient::
OnConfirmationClosedCallback>>()))
.Times(0);
client()->GetPaymentsAutofillClient()->CreditCardUploadCompleted(
payments::PaymentsAutofillClient::PaymentsRpcResult::kClientSideTimeout,
/*on_confirmation_closed_callback=*/std::nullopt);
}
#if !BUILDFLAG(IS_ANDROID)
TEST_F(ChromeAutofillClientTest, AutofillFieldIPH_NotShownByPromoController) {
SetUpIphForTesting(feature_engagement::kIPHAutofillAiOptInFeature);
EXPECT_CALL(*autofill_field_promo_controller(), IsMaybeShowing)
.WillRepeatedly(Return(false));
EXPECT_FALSE(client()->ShowAutofillFieldIphForFeature(
FormFieldData{}, AutofillClient::IphFeature::kAutofillAi));
}
TEST_F(ChromeAutofillClientTest, AutofillFieldIPH_IsShown) {
SetUpIphForTesting(feature_engagement::kIPHAutofillAiOptInFeature);
InSequence sequence;
EXPECT_CALL(*autofill_field_promo_controller(), IsMaybeShowing)
.WillOnce(Return(false));
EXPECT_CALL(*autofill_field_promo_controller(), Show);
EXPECT_CALL(*autofill_field_promo_controller(), IsMaybeShowing)
.WillOnce(Return(true));
EXPECT_TRUE(client()->ShowAutofillFieldIphForFeature(
FormFieldData{}, AutofillClient::IphFeature::kAutofillAi));
}
TEST_F(ChromeAutofillClientTest, AutofillImprovedPredictionsIPH_IsShown) {
SetUpIphForTesting(feature_engagement::kIPHAutofillAiOptInFeature);
InSequence sequence;
EXPECT_CALL(*autofill_field_promo_controller(), IsMaybeShowing)
.WillOnce(Return(false));
EXPECT_CALL(*autofill_field_promo_controller(), Show);
EXPECT_CALL(*autofill_field_promo_controller(), IsMaybeShowing)
.WillOnce(Return(true));
EXPECT_TRUE(client()->ShowAutofillFieldIphForFeature(
FormFieldData{}, AutofillClient::IphFeature::kAutofillAi));
}
TEST_F(ChromeAutofillClientTest,
AutofillFieldIPH_HideOnShowAutofillSuggestions) {
SetUpIphForTesting(feature_engagement::kIPHAutofillAiOptInFeature);
auto delegate = std::make_unique<MockAutofillSuggestionDelegate>();
EXPECT_CALL(*autofill_field_promo_controller(), Hide);
client()->ShowAutofillSuggestions(AutofillClient::PopupOpenArgs(),
delegate->GetWeakPtr());
// Showing the Autofill Popup is an asynchronous task.
task_environment()->RunUntilIdle();
testing::Mock::VerifyAndClearExpectations(autofill_field_promo_controller());
}
#endif // !BUILDFLAG(IS_ANDROID)
class ChromeAutofillClientTestWithWindow : public BrowserWithTestWindowTest {
public:
void SetUp() override {
user_ed_override_ =
BrowserWindowFeatures::GetUserDataFactoryForTesting()
.AddOverrideForTesting(
base::BindRepeating([](BrowserWindowInterface& window) {
return std::make_unique<MockBrowserUserEducationInterface>(
&window);
}));
BrowserWithTestWindowTest::SetUp();
// Create the first tab so that `web_contents()` exists.
AddTab(browser(), GURL(chrome::kChromeUINewTabURL));
}
MockBrowserUserEducationInterface* user_education() {
return static_cast<MockBrowserUserEducationInterface*>(
BrowserUserEducationInterface::From(browser()));
}
content::WebContents* web_contents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
TestChromeAutofillClient* client() {
return test_autofill_client_injector_[web_contents()];
}
private:
TestAutofillClientInjector<TestChromeAutofillClient>
test_autofill_client_injector_;
ui::UserDataFactory::ScopedOverride user_ed_override_;
};
TEST_F(ChromeAutofillClientTestWithWindow, AutofillFieldIPH_NotifyFeatureUsed) {
EXPECT_CALL(*user_education(),
NotifyFeaturePromoFeatureUsed(
Ref(feature_engagement::kIPHAutofillAiOptInFeature),
FeaturePromoFeatureUsedAction::kClosePromoIfPresent));
client()->NotifyIphFeatureUsed(AutofillClient::IphFeature::kAutofillAi);
}
#endif
#if BUILDFLAG(IS_ANDROID)
// Test that TouchToFill credit card filling triggers accessibility
// announcement.
TEST_F(ChromeAutofillClientTest,
DidFillForm_TouchToFillCreditCard_AnnouncesAccessibility) {
MockAutofillAccessibilityHelper* mock_ptr = SetUpMockAccessibilityHelper();
EXPECT_CALL(*mock_ptr, AnnounceTextForA11y(testing::_)).Times(1);
client()->DidFillForm(AutofillTriggerSource::kTouchToFillCreditCard,
/*is_refill=*/false);
TearDownMockAccessibilityHelper();
}
// Test that refill operations do not trigger accessibility announcements.
TEST_F(ChromeAutofillClientTest,
DidFillForm_TouchToFillCreditCardRefill_NoAccessibilityAnnouncement) {
MockAutofillAccessibilityHelper* mock_ptr = SetUpMockAccessibilityHelper();
EXPECT_CALL(*mock_ptr, AnnounceTextForA11y(testing::_)).Times(0);
client()->DidFillForm(AutofillTriggerSource::kTouchToFillCreditCard,
/*is_refill=*/true);
TearDownMockAccessibilityHelper();
}
// Test that non-TouchToFill trigger sources do not make accessibility
// announcements.
TEST_F(ChromeAutofillClientTest,
DidFillForm_OtherTriggerSource_NoAccessibilityAnnouncement) {
MockAutofillAccessibilityHelper* mock_ptr = SetUpMockAccessibilityHelper();
EXPECT_CALL(*mock_ptr, AnnounceTextForA11y(testing::_)).Times(0);
client()->DidFillForm(AutofillTriggerSource::kPopup,
/*is_refill=*/false);
TearDownMockAccessibilityHelper();
}
// Test that the correct localized accessibility message is announced.
TEST_F(ChromeAutofillClientTest, DidFillForm_VerifiesCorrectMessage) {
MockAutofillAccessibilityHelper* mock_ptr = SetUpMockAccessibilityHelper();
const std::u16string expected_message =
l10n_util::GetStringUTF16(IDS_AUTOFILL_A11Y_ANNOUNCE_FILLED_FORM);
EXPECT_CALL(*mock_ptr, AnnounceTextForA11y(expected_message)).Times(1);
client()->DidFillForm(AutofillTriggerSource::kTouchToFillCreditCard,
/*is_refill=*/false);
TearDownMockAccessibilityHelper();
}
#endif // BUILDFLAG(IS_ANDROID)
} // namespace
} // namespace autofill