blob: 3fcad2f39d511cd28156bce28c629c78b3998516 [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 "base/i18n/rtl.h"
#include "base/memory/weak_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/run_until.h"
#include "chrome/browser/ui/autofill/chrome_autofill_client.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/user_education/browser_user_education_interface.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/content/browser/test_autofill_client_injector.h"
#include "components/autofill/content/browser/test_autofill_driver_injector.h"
#include "components/autofill/core/browser/filling/filling_product.h"
#include "components/autofill/core/browser/foundations/autofill_client.h"
#include "components/autofill/core/browser/foundations/browser_autofill_manager.h"
#include "components/autofill/core/browser/foundations/browser_autofill_manager_test_api.h"
#include "components/autofill/core/browser/metrics/autofill_settings_metrics.h"
#include "components/autofill/core/browser/suggestions/suggestion.h"
#include "components/autofill/core/browser/suggestions/suggestion_type.h"
#include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
#include "components/autofill/core/browser/ui/autofill_external_delegate.h"
#include "components/autofill/core/browser/ui/popup_open_enums.h"
#include "components/autofill/core/common/aliases.h"
#include "components/autofill/core/common/autofill_test_utils.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/form_data_test_api.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/feature_engagement/test/scoped_iph_feature_list.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect_f.h"
#include "url/gurl.h"
namespace autofill {
namespace {
using ::testing::Not;
using ::testing::Optional;
using ::testing::Return;
class TestAutofillExternalDelegate : public AutofillExternalDelegate {
public:
explicit TestAutofillExternalDelegate(
BrowserAutofillManager* autofill_manager,
AutofillClient* autofill_client)
: AutofillExternalDelegate(autofill_manager), client_(*autofill_client) {}
~TestAutofillExternalDelegate() override = default;
void OnSuggestionsShown(base::span<const Suggestion>) override {
++show_counter_;
ui_session_id_at_last_show_ =
client_->GetSessionIdForCurrentAutofillSuggestions();
CHECK(ui_session_id_at_last_show_)
<< "session id should be non-empty directly after show";
}
FillingProduct GetMainFillingProduct() const override {
return FillingProduct::kAutocomplete;
}
int show_counter() const { return show_counter_; }
std::optional<AutofillClient::SuggestionUiSessionId>
ui_session_id_at_last_show() const {
return ui_session_id_at_last_show_;
}
private:
const raw_ref<AutofillClient> client_;
int show_counter_ = 0;
std::optional<AutofillClient::SuggestionUiSessionId>
ui_session_id_at_last_show_;
};
// 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 ChromeAutofillClientBrowserTest : public InProcessBrowserTest {
public:
void SetUpInProcessBrowserTestFixture() override {
// `BrowserWindow::MaybeShowFeaturePromo()` doesn't work in tests unless the
// IPH feature is explicitly enabled.
iph_feature_list_.InitAndEnableFeatures(
{feature_engagement::kIPHAutofillAiOptInFeature});
}
void SetUpOnMainThread() override {
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL("https://test.com")));
test_api(browser_autofill_manager())
.SetExternalDelegate(std::make_unique<TestAutofillExternalDelegate>(
&browser_autofill_manager(), client()));
}
TestAutofillExternalDelegate* aed() {
return static_cast<TestAutofillExternalDelegate*>(
test_api(browser_autofill_manager()).external_delegate());
}
TestChromeAutofillClient* client() {
return autofill_client_injector_[web_contents()];
}
ContentAutofillDriver* driver() {
return autofill_driver_injector_[web_contents()->GetPrimaryMainFrame()];
}
BrowserAutofillManager& browser_autofill_manager() {
return static_cast<BrowserAutofillManager&>(driver()->GetAutofillManager());
}
content::WebContents* web_contents() const {
return browser()->tab_strip_model()->GetActiveWebContents();
}
AutofillClient::SuggestionUiSessionId ShowSuggestions(
const gfx::RectF& bounds) {
return client()->ShowAutofillSuggestions(
ChromeAutofillClient::PopupOpenArgs(
bounds, base::i18n::TextDirection::LEFT_TO_RIGHT,
{Suggestion(u"test", SuggestionType::kAutocompleteEntry)},
AutofillSuggestionTriggerSource::kFormControlElementClicked,
/*form_control_ax_id=*/0, PopupAnchorType::kField),
test_api(browser_autofill_manager())
.external_delegate()
->GetWeakPtrForTest());
}
// Waits until the suggestions have been at least once more since calling this
// function.
void WaitUntilSuggestionsHaveBeenShown(
const base::Location& location = FROM_HERE) {
EXPECT_TRUE(base::test::RunUntil([this, initial_count =
suggestion_show_counter()]() {
return suggestion_show_counter() > initial_count;
})) << location.ToString()
<< ": Showing Autofill suggestions timed out.";
}
// Returns show many times the suggestions have been shown or updated.
int suggestion_show_counter() { return aed()->show_counter(); }
std::optional<AutofillClient::SuggestionUiSessionId>
ui_session_id_at_last_show() {
return aed()->ui_session_id_at_last_show();
}
private:
test::AutofillBrowserTestEnvironment autofill_test_environment_;
feature_engagement::test::ScopedIphFeatureList iph_feature_list_;
TestAutofillClientInjector<TestChromeAutofillClient>
autofill_client_injector_;
TestAutofillDriverInjector<ContentAutofillDriver> autofill_driver_injector_;
};
// This test displays an autofill field IPH, and then tries to show the Autofill
// Popup on top of it (they would overlap). The expected behaviour is
// that the IPH is hidden and the Autofill Popup is successfully shown.
IN_PROC_BROWSER_TEST_F(ChromeAutofillClientBrowserTest,
AutofillPopupIsShownIfOverlappingWithIph) {
FormData form = test::CreateTestAddressFormData();
test_api(form).field(0).set_bounds(gfx::RectF(10, 10));
client()->ShowAutofillFieldIphForFeature(
form.fields()[0], AutofillClient::IphFeature::kAutofillAi);
// Set the bounds such that the Autofill Popup would overlap with the IPH (the
// IPH is displayed right below `form.fields[0]`, whose bounds are set above).
ShowSuggestions(/*bounds=*/gfx::RectF(100, 100));
WaitUntilSuggestionsHaveBeenShown();
EXPECT_FALSE(
BrowserUserEducationInterface::From(browser())->IsFeaturePromoActive(
feature_engagement::kIPHAutofillAiOptInFeature));
}
IN_PROC_BROWSER_TEST_F(ChromeAutofillClientBrowserTest, SuggestionUiSessionId) {
// Before a popup is showing, no identifier is returned.
EXPECT_EQ(client()->GetSessionIdForCurrentAutofillSuggestions(),
std::nullopt);
// Showing suggestions leads (asynchronously) to showing a popup with the
// identifier returned by ShowAutofillSuggestions.
const AutofillClient::SuggestionUiSessionId first_id =
ShowSuggestions(gfx::RectF(50, 50));
WaitUntilSuggestionsHaveBeenShown();
EXPECT_THAT(ui_session_id_at_last_show(), std::make_optional(first_id));
const AutofillClient::SuggestionUiSessionId second_id =
ShowSuggestions(gfx::RectF(60, 60));
EXPECT_NE(first_id, second_id);
// Since showing suggestions is asynchronous, the identifier returned by
// ShowAutofillSuggestions can be different from the one currently showing.
EXPECT_THAT(client()->GetSessionIdForCurrentAutofillSuggestions(),
Not(Optional(second_id)));
// But once the new popup has been shown, they will be the same.
WaitUntilSuggestionsHaveBeenShown();
EXPECT_THAT(ui_session_id_at_last_show(), Optional(second_id));
// Updating the suggestions does not lead to a new identifier. Note that
// updating the suggestions is synchronous.
const int old_count = suggestion_show_counter();
const bool is_showing_suggestions =
client()->GetSessionIdForCurrentAutofillSuggestions().has_value();
client()->UpdateAutofillSuggestions(
{Suggestion(u"other text", SuggestionType::kAutocompleteEntry)},
FillingProduct::kAutocomplete,
AutofillSuggestionTriggerSource::kUnspecified);
EXPECT_GT(suggestion_show_counter(), old_count);
// It is possible that some external interaction with popup could have led
// to the suggestions hiding. In that case, updating the suggestions should
// be a no-op. Note that this is a "hack" to reduce flakiness for the test.
if (is_showing_suggestions) {
EXPECT_THAT(client()->GetSessionIdForCurrentAutofillSuggestions(),
Optional(second_id));
} else {
EXPECT_EQ(client()->GetSessionIdForCurrentAutofillSuggestions(),
std::nullopt);
}
}
IN_PROC_BROWSER_TEST_F(ChromeAutofillClientBrowserTest,
ShowAutofillSettings_RecordsMetrics) {
base::HistogramTester histogram_tester;
client()->ShowAutofillSettings(SuggestionType::kManageAddress);
histogram_tester.ExpectUniqueSample(
"Autofill.AddressesSettingsPage.VisitReferrer",
autofill_metrics::AutofillSettingsReferrer::kFillingFlowDropdown, 1);
client()->ShowAutofillSettings(SuggestionType::kManageCreditCard);
histogram_tester.ExpectUniqueSample(
"Autofill.PaymentMethodsSettingsPage.VisitReferrer",
autofill_metrics::AutofillSettingsReferrer::kFillingFlowDropdown, 1);
client()->ShowAutofillSettings(SuggestionType::kManageIban);
histogram_tester.ExpectUniqueSample(
"Autofill.PaymentMethodsSettingsPage.VisitReferrer",
autofill_metrics::AutofillSettingsReferrer::kFillingFlowDropdown, 2);
}
} // namespace
} // namespace autofill